From 7c3b4a1d285d35670593033d65df7cb148bc0783 Mon Sep 17 00:00:00 2001 From: Karolina Przerwa Date: Wed, 5 Nov 2025 11:33:15 +0100 Subject: [PATCH 1/9] feat(harvester): CDS DOI record update scenario --- .../inspire_harvester/transform_entry.py | 64 +- site/cds_rdm/inspire_harvester/writer.py | 191 +- site/tests/conftest.py | 65 +- site/tests/fake_datacite_client.py | 136 + site/tests/inspire_harvester/conftest.py | 69 +- .../data/record_with_cds_DOI.json | 133 + .../data/record_with_no_cds_DOI.json | 265 ++ ...ord_with_no_cds_DOI_multiple_doc_type.json | 2422 +++++++++++++++++ .../test_update_create_logic.py | 124 +- site/tests/inspire_harvester/test_writer.py | 32 - site/tests/inspire_harvester/utils.py | 2 +- 11 files changed, 3364 insertions(+), 139 deletions(-) create mode 100644 site/tests/fake_datacite_client.py create mode 100644 site/tests/inspire_harvester/data/record_with_cds_DOI.json create mode 100644 site/tests/inspire_harvester/data/record_with_no_cds_DOI.json create mode 100644 site/tests/inspire_harvester/data/record_with_no_cds_DOI_multiple_doc_type.json diff --git a/site/cds_rdm/inspire_harvester/transform_entry.py b/site/cds_rdm/inspire_harvester/transform_entry.py index f7e4cb9a..53c452bc 100644 --- a/site/cds_rdm/inspire_harvester/transform_entry.py +++ b/site/cds_rdm/inspire_harvester/transform_entry.py @@ -170,7 +170,6 @@ def _clean_identifiers(self): "OSTI", "SLAC", "PROQUEST", - "CDSRDM", ] external_sys_ids = self.inspire_metadata.get("external_system_identifiers", []) persistent_ids = self.inspire_metadata.get("persistent_identifiers", []) @@ -242,7 +241,16 @@ def _validate_imprint(self): def _transform_publisher(self): """Mapping of publisher.""" imprint = self._validate_imprint() - if not imprint: + DATACITE_PREFIX = current_app.config["DATACITE_PREFIX"] + dois = self.inspire_metadata.get("dois", []) + + has_cds_doi = next( + (d["value"] for d in dois if d["value"].startswith(DATACITE_PREFIX)), + False, + ) + if has_cds_doi and not imprint.get("publisher"): + return "CERN" + elif not imprint: return return imprint.get("publisher") @@ -319,23 +327,12 @@ def _transform_contributors(self): authors = self.inspire_metadata.get("authors", []) contributors = [] - corporate_authors = self.inspire_metadata.get("corporate_author", []) - mapped_corporate_authors = [] - for corporate_author in corporate_authors: - contributor = { - "person_or_org": { - "type": "organizational", - "name": corporate_author, - }, - } - mapped_corporate_authors.append(contributor) - for author in authors: inspire_roles = author.get("inspire_roles", []) if "supervisor" in inspire_roles: contributors.append(author) - return self._transform_creatibutors(contributors) + mapped_corporate_authors + return self._transform_creatibutors(contributors) def _transform_creators(self): """Mapping of INSPIRE authors to creators.""" @@ -348,7 +345,18 @@ def _transform_creators(self): elif "author" in inspire_roles or "editor" in inspire_roles: creators.append(author) - return self._transform_creatibutors(creators) + corporate_authors = self.inspire_metadata.get("corporate_author", []) + mapped_corporate_authors = [] + for corporate_author in corporate_authors: + contributor = { + "person_or_org": { + "type": "organizational", + "name": corporate_author, + }, + } + mapped_corporate_authors.append(contributor) + + return self._transform_creatibutors(creators) + mapped_corporate_authors def _transform_creatibutors(self, authors): """Transform creatibutors.""" @@ -464,7 +472,7 @@ def _transform_dois(self): """Mapping of record dois.""" DATACITE_PREFIX = current_app.config["DATACITE_PREFIX"] dois = self.inspire_metadata.get("dois", []) - mapped_dois = [] + if not dois: return @@ -498,28 +506,6 @@ def _transform_dois(self): f"DOI validation failed. DOI#{doi}. INSPIRE#{self.inspire_id}." ) return None - # for doi_obj in unique_dois: - # doi = doi_obj.get("value") - # material = doi_obj.get("material") - # - # is_cern = doi.startswith(DATACITE_PREFIX) - # - # if is_doi(doi): - # mapped_doi = { - # "identifier": doi, - # } - # if is_cern: - # mapped_doi["provider"] = "datacite" - # else: - # mapped_doi["provider"] = "external" - # mapped_doi["_material"] = material - # mapped_dois.append(mapped_doi) - # else: - # self.metadata_errors.append( - # f"DOI validation failed. DOI#{doi}. INSPIRE#{self.inspire_id}." - # ) - if mapped_dois: - return mapped_dois def _transform_identifiers(self): identifiers = [] @@ -535,6 +521,8 @@ def _transform_identifiers(self): for external_sys_id in external_sys_ids: schema = external_sys_id.get("schema").lower() value = external_sys_id.get("value") + if schema == "cdsrdm": + schema = "cds" if schema in RDM_RECORDS_IDENTIFIERS_SCHEMES.keys(): identifiers.append({"identifier": value, "scheme": schema}) elif schema in RDM_RECORDS_RELATED_IDENTIFIERS_SCHEMES.keys(): diff --git a/site/cds_rdm/inspire_harvester/writer.py b/site/cds_rdm/inspire_harvester/writer.py index bbe8d8ac..2b7bb6e5 100644 --- a/site/cds_rdm/inspire_harvester/writer.py +++ b/site/cds_rdm/inspire_harvester/writer.py @@ -8,6 +8,7 @@ """Writer module.""" import logging import time +from collections import OrderedDict from io import BytesIO import requests @@ -39,8 +40,6 @@ def _write_entry(self, stream_entry, *args, inspire_id=None, logger=None, **kwar existing_records_hits = existing_records.to_dict()["hits"]["hits"] existing_records_ids = [hit["id"] for hit in existing_records_hits] - logger.debug("Found {0} existing records".format(existing_records.total)) - if multiple_records_found: msg = "Multiple records match: {0}".format(", ".join(existing_records_ids)) @@ -70,7 +69,7 @@ def _write_entry(self, stream_entry, *args, inspire_id=None, logger=None, **kwar @hlog def _process_entry( - self, stream_entry, *args, inspire_id=None, logger=None, **kwargs + self, stream_entry, *args, inspire_id=None, logger=None, **kwargs ): """Helper method to process a single entry.""" error_message = None @@ -105,28 +104,90 @@ def write_many(self, stream_entries, *args, **kwargs): current_app.logger.info(f"All entries processed.") return stream_entries + def _retrieve_identifier(self, identifiers, scheme): + """Retrieve identifier by scheme.""" + return next( + ( + d["identifier"] + for d in identifiers + if d["scheme"] == scheme + ), + None, + ) + @hlog def _get_existing_records( - self, stream_entry, inspire_id=None, logger=None, record_pid=None + self, stream_entry, inspire_id=None, logger=None, record_pid=None ): """Find records that have already been harvested from INSPIRE.""" - # for now checking only by inspire id - filters = [ + entry = stream_entry.entry + + doi = entry["pids"].get("doi", {}).get("identifier") + related_identifiers = entry["metadata"].get("related_identifiers", []) + + cds_id = self._retrieve_identifier(related_identifiers, "cds") + arxiv_id = self._retrieve_identifier(related_identifiers, "arxiv") + report_number = self._retrieve_identifier(related_identifiers, "cdsrn") + + doi_filters = [ + dsl.Q("term", **{"pids.doi.identifier.keyword": doi}), + ] + + cds_filters = [ + dsl.Q("term", **{"id": cds_id}), + ] + + inspire_filters = [ dsl.Q("term", **{"metadata.related_identifiers.scheme": "inspire"}), dsl.Q("term", **{"metadata.related_identifiers.identifier": inspire_id}), ] - combined_filter = dsl.Q("bool", filter=filters) - logger.debug(f"Searching for existing records: {filters}") - result = current_rdm_records_service.search( - system_identity, extra_filter=combined_filter + cds_identifiers_filters = [ + dsl.Q("term", **{"metadata.identifiers.scheme": "cds"}), + dsl.Q("term", **{"metadata.identifiers.identifier": cds_id}), + ] + + arxiv_filters = [ + dsl.Q("term", **{"metadata.related_identifiers.scheme": "arxiv"}), + dsl.Q("term", **{"metadata.related_identifiers.identifier": arxiv_id}), + ] + + report_number_filters = [ + dsl.Q("term", **{"metadata.related_identifiers.scheme": "cdsrn"}), + dsl.Q("term", **{"metadata.related_identifiers.identifier": report_number}), + ] + + filters_priority = OrderedDict( + doi={"filter": doi_filters, "value": doi}, + cds_pid={"filter": cds_filters, "value": cds_id}, + cds_identifiers={"filter": cds_identifiers_filters, "value": cds_id}, + inspire_id={"filter": inspire_filters, "value": inspire_id}, + arxiv_filters={"filter": arxiv_filters, "value": arxiv_id}, + report_number_filters={ + "filter": report_number_filters, + "value": report_number, + }, ) - logger.debug(f"Found {result.total} matching records") + + for filter_key, filter in filters_priority.items(): + + if filter["value"]: + combined_filter = dsl.Q("bool", filter=filter["filter"]) + logger.debug(f"Searching for existing records: {filter['filter']}") + + result = current_rdm_records_service.search( + system_identity, extra_filter=combined_filter + ) + + if result.total >= 1: + logger.debug(f"Found {result.total} matching records.") + break + return result @hlog def update_record( - self, stream_entry, record_pid=None, inspire_id=None, logger=None + self, stream_entry, record_pid=None, inspire_id=None, logger=None ): """Update existing record.""" entry = stream_entry.entry @@ -151,13 +212,17 @@ def update_record( logger.debug(f"New files' checksums: {new_checksums}.") has_external_doi = ( - record.data["pids"].get("doi", {}).get("provider") == "external" + record.data["pids"].get("doi", {}).get("provider") == "external" ) should_create_new_version = ( - existing_checksums != new_checksums and not has_external_doi + existing_checksums != new_checksums and not has_external_doi ) should_update_files = existing_checksums != new_checksums and has_external_doi + files_enabled = record_dict.get("files", {}).get("enabled", False) + if should_update_files and not files_enabled: + stream_entry.entry["files"]["enabled"] = True + if should_create_new_version: self._create_new_version(stream_entry, record) @@ -196,21 +261,22 @@ def update_record( raise WriterError( f"ERROR: Draft {draft['id']} not published, validation errors: {e.messages}." ) - # except Exception as e: - # current_rdm_records_service.delete_draft(system_identity, draft["id"]) + except Exception as e: + current_rdm_records_service.delete_draft(system_identity, draft["id"]) + raise e # raise WriterError( # f"Draft {draft.id} failed publishing because of an unexpected error: {str(e)}." # ) @hlog def _update_files( - self, - stream_entry, - new_draft, - record, - record_pid=None, - inspire_id=None, - logger=None, + self, + stream_entry, + new_draft, + record, + record_pid=None, + inspire_id=None, + logger=None, ): entry = stream_entry.entry @@ -261,7 +327,7 @@ def _update_files( @hlog def _create_new_version( - self, stream_entry, record, inspire_id=None, record_pid=None, logger=None + self, stream_entry, record, inspire_id=None, record_pid=None, logger=None ): """For records with updated files coming from INSPIRE, create and publish a new version.""" entry = stream_entry.entry @@ -269,17 +335,23 @@ def _create_new_version( system_identity, record["id"] ) + # delete the previous DOI for new version + del entry["pids"] logger.debug(f"New version draft created with ID: {new_version_draft.id}") - current_rdm_records_service.import_files(system_identity, new_version_draft.id) + new_version_draft = current_rdm_records_service.update_draft( + system_identity, new_version_draft.id, entry + ) + + if record.data.get("files", {}).get("enabled", False): + current_rdm_records_service.import_files( + system_identity, new_version_draft.id + ) logger.debug(f"Imported files from previous version: {new_version_draft.id}") self._update_files(stream_entry, new_version_draft, record) - current_rdm_records_service.update_draft( - system_identity, new_version_draft.id, entry - ) logger.debug(f"New version metadata updated: {new_version_draft.id}") try: logger.debug("Publishing new version draft") @@ -313,7 +385,7 @@ def _create_new_version( @hlog def _add_community( - self, stream_entry, draft, inspire_id=None, record_pid=None, logger=None + self, stream_entry, draft, inspire_id=None, record_pid=None, logger=None ): """Add CERN Scientific Community to the draft.""" with db.session.begin_nested(): @@ -329,11 +401,17 @@ def _add_community( @hlog def _create_new_record( - self, stream_entry, record_pid=None, inspire_id=None, logger=None + self, stream_entry, record_pid=None, inspire_id=None, logger=None ): """For new records coming from INSPIRE, create and publish a draft in CDS.""" entry = stream_entry.entry + doi = entry["pids"].get("doi", {}) + DATACITE_PREFIX = current_app.config["DATACITE_PREFIX"] + is_cds = DATACITE_PREFIX in doi["identifier"] + if is_cds: + raise WriterError("Trying to create record with CDS DOI") + file_entries = entry["files"].get("entries", None) logger.debug(f"Files to create: {len(file_entries) if file_entries else 0}") @@ -366,10 +444,10 @@ def _create_new_record( current_rdm_records_service.delete_draft(system_identity, draft["id"]) logger.info(f"Draft {draft.id} is deleted due to errors.") - - raise WriterError( - f"Failure: draft {draft.id} not created, unexpected error: {str(e)}." - ) + raise e + # raise WriterError( + # f"Failure: draft {draft.id} not created, unexpected error: {str(e)}." + # ) else: try: self._add_community(stream_entry, draft) @@ -392,19 +470,20 @@ def _create_new_record( ) except Exception as e: current_rdm_records_service.delete_draft(system_identity, draft["id"]) - raise WriterError( - f"Failure: draft {draft.id} not published, unexpected error: {str(e)}." - ) + raise e + # raise WriterError( + # f"Failure: draft {draft.id} not published, unexpected error: {str(e)}." + # ) @hlog def _fetch_file( - self, - stream_entry, - inspire_url, - max_retries=3, - inspire_id=None, - record_pid=None, - logger=None, + self, + stream_entry, + inspire_url, + max_retries=3, + inspire_id=None, + record_pid=None, + logger=None, ): """Fetch file content from inspire url.""" logger.debug(f"File URL: {inspire_url}") @@ -446,18 +525,19 @@ def _fetch_file( @hlog def _create_file( - self, - stream_entry, - file_data, - file_content, - draft, - inspire_id=None, - record_pid=None, - logger=None, + self, + stream_entry, + file_data, + file_content, + draft, + inspire_id=None, + record_pid=None, + logger=None, ): """Create a new file.""" logger.debug(f"Filename: '{file_data['key']}'.") service = current_rdm_records_service + try: service.draft_files.init_files( system_identity, @@ -504,6 +584,7 @@ def _create_file( service.draft_files.delete_file(system_identity, draft.id, file_data["key"]) - raise WriterError( - f"File {file_data['key']} creation failed because of an unexpected error: {str(e)}." - ) + raise e + # raise WriterError( + # f"File {file_data['key']} creation failed because of an unexpected error: {str(e)}." + # ) diff --git a/site/tests/conftest.py b/site/tests/conftest.py index ff9b5248..08cee277 100644 --- a/site/tests/conftest.py +++ b/site/tests/conftest.py @@ -36,6 +36,8 @@ RDM_RECORDS_RELATED_IDENTIFIERS_SCHEMES, always_valid, ) +from invenio_rdm_records.resources.serializers import DataCite43JSONSerializer +from invenio_rdm_records.services.pids import providers from invenio_records_resources.proxies import current_service_registry from invenio_users_resources.records.api import UserAggregate from invenio_vocabularies.config import ( @@ -61,6 +63,8 @@ ) from cds_rdm.schemes import is_cds, is_inspire, is_inspire_author +from .fake_datacite_client import FakeDataCiteClient + pytest_plugins = ("celery.contrib.pytest",) @@ -84,6 +88,12 @@ def load(self, filepath): return MockJinjaManifest() +@pytest.fixture(scope="module") +def mock_datacite_client(): + """Mock DataCite client.""" + return FakeDataCiteClient + + @pytest.fixture(scope="module") def community_service(app): """Community service.""" @@ -118,10 +128,12 @@ def scientific_community(community_service, minimal_community): @pytest.fixture(scope="module") -def app_config(app_config): +def app_config(app_config, mock_datacite_client): """Mimic an instance's configuration.""" app_config["REST_CSRF_ENABLED"] = True app_config["DATACITE_ENABLED"] = True + app_config["DATACITE_USERNAME"] = "INVALID" + app_config["DATACITE_PASSWORD"] = "INVALID" app_config["DATACITE_PREFIX"] = "10.17181" app_config["OAUTH_REMOTE_APP_NAME"] = "cern" app_config["CERN_APP_CREDENTIALS"] = { @@ -219,6 +231,35 @@ def app_config(app_config): app_config["CDS_INSPIRE_IDS_SCHEMES_MAPPING"] = { "hdl": "handle", } + app_config["RDM_PERSISTENT_IDENTIFIER_PROVIDERS"] = [ + # DataCite DOI provider with fake client + providers.DataCitePIDProvider( + "datacite", + client=mock_datacite_client("datacite", config_prefix="DATACITE"), + label=_("DOI"), + ), + # DOI provider for externally managed DOIs + providers.ExternalPIDProvider( + "external", + "doi", + validators=[providers.BlockedPrefixes(config_names=["DATACITE_PREFIX"])], + label=_("DOI"), + ), + # OAI identifier + providers.OAIPIDProvider( + "oai", + label=_("OAI ID"), + ), + ] + app_config["RDM_PARENT_PERSISTENT_IDENTIFIER_PROVIDERS"] = [ + # DataCite Concept DOI provider + providers.DataCitePIDProvider( + "datacite", + client=mock_datacite_client("datacite", config_prefix="DATACITE"), + serializer=DataCite43JSONSerializer(schema_context={"is_parent": True}), + label=_("Concept DOI"), + ), + ] return app_config @@ -685,6 +726,28 @@ def resource_type_v(app, resource_type_type): }, ) + vocabulary_service.create( + system_identity, + { + "id": "publication-article", + "icon": "file alternate", + "props": { + "csl": "article", + "datacite_general": "Artice", + "datacite_type": "", + "openaire_resourceType": "0044", + "openaire_type": "publication", + "eurepo": "info:eu-repo/semantics/other", + "schema.org": "https://schema.org/Article", + "subtype": "publication-article", + "type": "publication", + }, + "title": {"en": "Article", "de": "Artikel"}, + "tags": ["depositable", "linkable"], + "type": "resourcetypes", + }, + ) + vocabulary_service.create( system_identity, { diff --git a/site/tests/fake_datacite_client.py b/site/tests/fake_datacite_client.py new file mode 100644 index 00000000..da0edae5 --- /dev/null +++ b/site/tests/fake_datacite_client.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2022 Northwestern University. +# +# Invenio-RDM-Records is free software; you can redistribute it and/or modify +# it under the terms of the MIT License; see LICENSE file for more details. + +"""DataCite DOI Client.""" + +from unittest.mock import Mock + +from idutils import normalize_doi +from invenio_rdm_records.services.pids import providers + + +class FakeDataCiteRESTClient: + """DataCite REST API client wrapper.""" + + def __init__( + self, username, password, prefix, test_mode=False, url=None, timeout=None + ): + """Initialize the REST client wrapper. + + :param username: DataCite username. + :param password: DataCite password. + :param prefix: DOI prefix (or CFG_DATACITE_DOI_PREFIX). + :param test_mode: use test URL when True + :param url: DataCite API base URL. + :param timeout: Connect and read timeout in seconds. Specify a tuple + (connect, read) to specify each timeout individually. + """ + self.username = str(username) + self.password = str(password) + self.prefix = str(prefix) + + if test_mode: + self.api_url = "https://api.test.datacite.org/" + else: + self.api_url = url or "https://api.datacite.org/" + + if not self.api_url.endswith("/"): + self.api_url += "/" + + self.timeout = timeout + + def public_doi(self, metadata, url, doi=None): + """Create a public doi ... not. + + :param metadata: JSON format of the metadata. + :param doi: DOI (e.g. 10.123/456) + :param url: URL where the doi will resolve. + :return: + """ + return Mock() + + def update_doi(self, doi, metadata=None, url=None): + """Update the metadata or url for a DOI ... not. + + :param url: URL where the doi will resolve. + :param metadata: JSON format of the metadata. + :return: + """ + return Mock() + + def delete_doi(self, doi): + """Delete a doi ... not. + + This will only work for draft dois + + :param doi: DOI (e.g. 10.123/456) + :return: + """ + return Mock() + + def hide_doi(self, doi): + """Hide a previously registered DOI ... not. + + This DOI will no + longer be found in DataCite Search + + :param doi: DOI to hide e.g. 10.12345/1. + :return: + """ + return Mock() + + def show_doi(self, doi): + """Show a previously hidden DOI ... not. + + This DOI will no + longer be found in DataCite Search + + :param doi: DOI to hide e.g. 10.12345/1. + :return: + """ + return Mock() + + def check_doi(self, doi): + """Check doi structure. + + Check that the doi has a form + 12.12345/123 with the prefix defined + """ + # If prefix is in doi + if "/" in doi: + split = doi.split("/") + prefix = split[0] + if prefix != self.prefix: + # Provided a DOI with the wrong prefix + raise ValueError( + "Wrong DOI {0} prefix provided, it should be " + "{1} as defined in the rest client".format(prefix, self.prefix) + ) + else: + doi = f"{self.prefix}/{doi}" + return normalize_doi(doi) + + def __repr__(self): + """Create string representation of object.""" + return f"" + + +class FakeDataCiteClient(providers.DataCiteClient): + """Fake DataCite Client.""" + + @property + def api(self): + """DataCite REST API client instance.""" + if self._api is None: + self.check_credentials() + self._api = FakeDataCiteRESTClient( + self.cfg("username"), + self.cfg("password"), + self.cfg("prefix"), + self.cfg("test_mode", True), + ) + return self._api diff --git a/site/tests/inspire_harvester/conftest.py b/site/tests/inspire_harvester/conftest.py index 4d576ae0..9cf631f4 100644 --- a/site/tests/inspire_harvester/conftest.py +++ b/site/tests/inspire_harvester/conftest.py @@ -40,5 +40,70 @@ def datastream_config(): } -def existing_cds_migrated_record(running_app): - """Create a test record.""" +@pytest.fixture(scope="function") +def transformed_record_no_files(): + """Transformed via InspireJsonTransformer record with no files.""" + return { + "id": "1695540", + "metadata": { + "title": "Helium II heat transfer in LHC magnets", + "additional_titles": [ + {"title": "Polyimide cable insulation", "type": {"id": "subtitle"}} + ], + "publication_date": "2017", + "resource_type": {"id": "publication-dissertation"}, + "creators": [ + {"person_or_org": {"type": "personal", "family_name": "Hanks, Tom"}}, + {"person_or_org": {"type": "personal", "family_name": "Potter, Harry"}}, + {"person_or_org": {"type": "personal", "family_name": "Weasley, Ron"}}, + ], + "related_identifiers": [ + { + "identifier": "1695540", + "scheme": "inspire", + "relation_type": {"id": "isversionof"}, + "resource_type": {"id": "publication-other"}, + } + ], + }, + "files": {"enabled": False}, + "parent": {"access": {"owned_by": {"user": 2}}}, + "access": {"record": "public", "files": "public"}, + } + + +@pytest.fixture() +def minimal_record(): + """Minimal record data as dict coming from the external world.""" + return { + "pids": {}, + "access": { + "record": "public", + "files": "public", + }, + "files": { + "enabled": False, # Most tests don't care about files + }, + "metadata": { + "creators": [ + { + "person_or_org": { + "family_name": "Brown", + "given_name": "Troy", + "type": "personal", + } + }, + { + "person_or_org": { + "name": "Troy Inc.", + "type": "organizational", + }, + }, + ], + "publication_date": "2020-06-01", + # because DATACITE_ENABLED is True, this field is required + "publisher": "Acme Inc", + "resource_type": {"id": "image-photo"}, + "title": "A Romans story", + }, + } diff --git a/site/tests/inspire_harvester/data/record_with_cds_DOI.json b/site/tests/inspire_harvester/data/record_with_cds_DOI.json new file mode 100644 index 00000000..591b201b --- /dev/null +++ b/site/tests/inspire_harvester/data/record_with_cds_DOI.json @@ -0,0 +1,133 @@ +{ + "hits": { + "total": 1, + "hits": [ + { + "revision_id": 5, + "metadata": { + "citation_count": 50, + "citation_count_without_self_citations": 33, + "documents": [ + { + "key": "4550b6ee36afc3fdedc08d0423375ab4", + "url": "https://inspirehep.net/files/3174c5b6b663f81a00e5f85925b0f7e0", + "source": "CDS", + "filename": "LHCB-TDR-017.pdf", + "fulltext": true + } + ], + "report_numbers": [ + { + "value": "CERN-LHCC-2018-007", + "source": "CDS" + }, + { + "value": "LHCB-TDR-017", + "source": "CDS" + } + ], + "core": false, + "dois": [ + { + "value": "10.17181/CERN.LELX.5VJY", + "source": "CDS" + }, + { + "value": "10.17181/CERN.LELX.5VJY" + } + ], + "isbns": [ + { + "value": "9789290834786", + "medium": "print" + }, + { + "value": "9789290834793", + "medium": "online" + } + ], + "titles": [ + { + "title": "Upgrade Software and Computing", + "source": "CDS" + } + ], + "$schema": "https://inspirehep.net/schemas/records/hep.json", + "curated": true, + "texkeys": [ + "LHCb:2018pqv" + ], + "imprints": [ + { + "date": "2018" + } + ], + "abstracts": [ + { + "value": "This document reports the Research and Development activities that are carried out in the software and computing domains in view of the upgrade of the LHCb experiment. The implementation of a full software trigger implies major changes in the core software framework, in the event data model, and in the reconstruction algorithms. The increase of the data volumes for both real and simulated datasets requires a corresponding scaling of the distributed computing infrastructure. An implementation plan in both domains is presented, together with a risk assessment analysis.", + "source": "CDS" + } + ], + "copyright": [ + { + "holder": "CC-BY-4.0", + "material": "preprint" + } + ], + "document_type": [ + "article" + ], + "collaborations": [ + { + "value": "LHCb", + "record": { + "$ref": "https://inspirehep.net/api/experiments/1110643" + } + } + ], + "control_number": 2707794, + "number_of_pages": 102, + "corporate_author": [ + "CERN. Geneva. The LHC experiments Committee" + ], + "inspire_categories": [ + { + "term": "Instrumentation", + "source": "cds" + } + ], + "accelerator_experiments": [ + { + "legacy_name": "CERN-LHC-LHCb" + }, + { + "record": { + "$ref": "https://inspirehep.net/api/experiments/1110643" + }, + "legacy_name": "CERN-LHC-LHCb" + } + ], + "external_system_identifiers": [ + { + "value": "2310827", + "schema": "CDS" + } + ] + }, + "id": "2707794", + "updated": "2024-11-18T11:42:37.849066+00:00", + "uuid": "709de307-6d94-4ff3-8e15-f53de36f5602", + "links": { + "bibtex": "https://inspirehep.net/api/literature/2707794?format=bibtex", + "latex-eu": "https://inspirehep.net/api/literature/2707794?format=latex-eu", + "latex-us": "https://inspirehep.net/api/literature/2707794?format=latex-us", + "json": "https://inspirehep.net/api/literature/2707794?format=json", + "json-expanded": "https://inspirehep.net/api/literature/2707794?format=json-expanded", + "cv": "https://inspirehep.net/api/literature/2707794?format=cv", + "citations": "https://inspirehep.net/api/literature/?q=refersto%3Arecid%3A2707794" + }, + "created": "2023-10-09T13:00:50.299747+00:00" + } + ] + } +} \ No newline at end of file diff --git a/site/tests/inspire_harvester/data/record_with_no_cds_DOI.json b/site/tests/inspire_harvester/data/record_with_no_cds_DOI.json new file mode 100644 index 00000000..b1657720 --- /dev/null +++ b/site/tests/inspire_harvester/data/record_with_no_cds_DOI.json @@ -0,0 +1,265 @@ +{ + "hits": { + "total": 1, + "hits": [ + { + "revision_id": 89, + "created": "2019-07-23T00:00:00+00:00", + "links": { + "bibtex": "https://inspirehep.net/api/literature/1745667?format=bibtex", + "latex-eu": "https://inspirehep.net/api/literature/1745667?format=latex-eu", + "latex-us": "https://inspirehep.net/api/literature/1745667?format=latex-us", + "json": "https://inspirehep.net/api/literature/1745667?format=json", + "json-expanded": "https://inspirehep.net/api/literature/1745667?format=json-expanded", + "cv": "https://inspirehep.net/api/literature/1745667?format=cv", + "citations": "https://inspirehep.net/api/literature/?q=refersto%3Arecid%3A1745667" + }, + "metadata": { + "citation_count_without_self_citations": 0, + "citation_count": 0, + "publication_info": [ + { + "cnum": "C19-05-19.1", + "year": 2019, + "artid": "THPGW094", + "conf_acronym": "IPAC2019", + "parent_record": { + "$ref": "https://inspirehep.net/api/literature/1743327" + }, + "conference_record": { + "$ref": "https://inspirehep.net/api/conferences/1732175" + } + } + ], + "documents": [ + { + "key": "6a3ac417f0ef905b65a0a166ef5f7c49", + "url": "https://inspirehep.net/files/6a3ac417f0ef905b65a0a166ef5f7c49", + "filename": "thpgw094.pdf", + "description": "Fulltext from publisher" + } + ], + "dois": [ + { + "value": "10.18429/JACoW-IPAC2019-THPGW094", + "source": "JACoW" + } + ], + "titles": [ + { + "title": "Phasing of superconductive cavities of the REX/HIE-ISOLDE linac", + "source": "JACoW" + } + ], + "$schema": "https://inspirehep.net/schemas/records/hep.json", + "authors": [ + { + "ids": [ + { + "value": "JACoW-00022225", + "schema": "JACOW" + } + ], + "uuid": "bd788569-e69e-4ae9-ae45-be9447988039", + "emails": [ + "emanuele.matli@cern.ch" + ], + "record": { + "$ref": "https://inspirehep.net/api/authors/1643095" + }, + "full_name": "Matli, Emanuele", + "affiliations": [ + { + "value": "CERN", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902725" + } + } + ], + "signature_block": "MATLe", + "raw_affiliations": [ + { + "value": "CERN, Geneva, Switzerland" + } + ] + }, + { + "ids": [ + { + "value": "JACoW-00088975", + "schema": "JACOW" + } + ], + "uuid": "369ac0eb-bae1-4a8d-b38b-ae15c7a33b8a", + "emails": [ + "niels.killian.noal.bidault@cern.ch" + ], + "record": { + "$ref": "https://inspirehep.net/api/authors/2013858" + }, + "full_name": "Bidault, Niels", + "affiliations": [ + { + "value": "CERN", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902725" + } + }, + { + "value": "U. Rome La Sapienza (main)", + "record": { + "$ref": "https://inspirehep.net/api/institutions/945357" + } + }, + { + "value": "INFN, Rome", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902887" + } + } + ], + "signature_block": "BADALTn", + "raw_affiliations": [ + { + "value": "CERN, Meyrin, Switzerland" + } + ] + }, + { + "ids": [ + { + "value": "JACoW-00033601", + "schema": "JACOW" + } + ], + "uuid": "a57a7e0a-f3e3-4ccb-ba72-7487d54bcd81", + "emails": [ + "episelli@cern.ch" + ], + "record": { + "$ref": "https://inspirehep.net/api/authors/1647432" + }, + "full_name": "Piselli, Emiliano", + "signature_block": "PASALe", + "raw_affiliations": [ + { + "value": "CERN, Meyrin, Switzerland" + } + ] + }, + { + "ids": [ + { + "value": "JACoW-00002493", + "schema": "JACOW" + } + ], + "uuid": "3eff49d9-da3c-4467-9472-44b2c234920e", + "emails": [ + "alberto.rodriguez@cern.ch" + ], + "record": { + "$ref": "https://inspirehep.net/api/authors/1929244" + }, + "full_name": "Rodriguez, Jose", + "affiliations": [ + { + "value": "CERN", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902725" + } + } + ], + "signature_block": "RADRAGj", + "raw_affiliations": [ + { + "value": "CERN, Meyrin, Switzerland" + } + ] + } + ], + "curated": true, + "license": [ + { + "url": "http://creativecommons.org/licenses/by/3.0/", + "license": "CC-BY-3.0", + "imposing": "JACoW", + "material": "publication" + } + ], + "texkeys": [ + "Matli:2019rbd" + ], + "imprints": [ + { + "date": "2019-06-21" + } + ], + "keywords": [ + { + "value": "cavity", + "schema": "JACOW" + }, + { + "value": "detector", + "schema": "JACOW" + }, + { + "value": "linac", + "schema": "JACOW" + }, + { + "value": "ISOL", + "schema": "JACOW" + }, + { + "value": "dipole", + "schema": "JACOW" + } + ], + "abstracts": [ + { + "value": "ISOLDE is a facility dedicated to the production of a large variety of Radioactive Ion Beams. The facility is located at the European Organization for Nuclear Research (CERN). In addition to two target stations followed by low energy separators, the facility includes a post-accelerating linac with both normal conducting (REX) and superconducting (HIE-ISOLDE) sections. The HIE-ISOLDE section consists of four cryomodules with five SRF cavities each that need to be phased individually. In this paper, we will describe the procedure and the software applications developed to phase each of the cavities as well as improvements that will be introduced in the near future to reduce the time it takes to complete the process.", + "source": "JACoW" + } + ], + "document_type": [ + "conference paper" + ], + "control_number": 1745667, + "legacy_version": "20191011042008.0", + "number_of_pages": 3, + "inspire_categories": [ + { + "term": "Accelerators" + } + ], + "legacy_creation_date": "2019-07-23", + "accelerator_experiments": [ + { + "record": { + "$ref": "https://inspirehep.net/api/experiments/1110538" + }, + "legacy_name": "CERN-REX-ISOLDE" + }, + { + "record": { + "$ref": "https://inspirehep.net/api/experiments/1614014" + }, + "legacy_name": "CERN-HIE-ISOLDE" + } + ], + "external_system_identifiers": [ + { + "value": "2693005", + "schema": "CDS" + } + ] + }, + "updated": "2023-03-09T20:35:11.219721+00:00", + "id": "1745667", + "uuid": "d9e307d5-30a1-4812-9dc8-69631f2e105f" + } + ] + } +} \ No newline at end of file diff --git a/site/tests/inspire_harvester/data/record_with_no_cds_DOI_multiple_doc_type.json b/site/tests/inspire_harvester/data/record_with_no_cds_DOI_multiple_doc_type.json new file mode 100644 index 00000000..dd673297 --- /dev/null +++ b/site/tests/inspire_harvester/data/record_with_no_cds_DOI_multiple_doc_type.json @@ -0,0 +1,2422 @@ +{ + "hits": { + "total": 1, + "hits": [ + { + "id": "1992535", + "links": { + "bibtex": "https://inspirehep.net/api/literature/1992535?format=bibtex", + "latex-eu": "https://inspirehep.net/api/literature/1992535?format=latex-eu", + "latex-us": "https://inspirehep.net/api/literature/1992535?format=latex-us", + "json": "https://inspirehep.net/api/literature/1992535?format=json", + "json-expanded": "https://inspirehep.net/api/literature/1992535?format=json-expanded", + "cv": "https://inspirehep.net/api/literature/1992535?format=cv", + "citations": "https://inspirehep.net/api/literature/?q=refersto%3Arecid%3A1992535" + }, + "revision_id": 29, + "updated": "2025-06-05T07:26:20.266932+00:00", + "metadata": { + "citation_count": 36, + "publication_info": [ + { + "year": 2021, + "artid": "L111102", + "material": "publication", + "journal_issue": "11", + "journal_title": "Phys.Rev.D", + "journal_record": { + "$ref": "https://inspirehep.net/api/journals/1613970" + }, + "journal_volume": "104" + } + ], + "documents": [ + { + "key": "26aec07fc9c6bea56cde13f86d74d96b", + "url": "https://inspirehep.net/files/26aec07fc9c6bea56cde13f86d74d96b", + "filename": "PhysRevD.104.L111102.pdf", + "fulltext": true, + "material": "publication" + } + ], + "citation_count_without_self_citations": 28, + "core": true, + "dois": [ + { + "value": "10.1103/PhysRevD.104.L111102", + "source": "APS", + "material": "publication" + }, + { + "value": "10.1103/PhysRevD.104.L111102", + "source": "arXiv", + "material": "publication" + } + ], + "titles": [ + { + "title": "Search for pseudoscalar bosons decaying into $e^+e^-$ pairs in the NA64 experiment at the CERN SPS", + "source": "APS" + }, + { + "title": "Search for pseudoscalar bosons decaying into $e^+e^-$ pairs in the NA64 experiment at the CERN SPS", + "source": "arXiv" + }, + { + "title": "Search for pseudoscalar bosons decaying into $e^+e^-$ pairs", + "source": "arXiv" + } + ], + "$schema": "https://inspirehep.net/schemas/records/hep.json", + "authors": [ + { + "uuid": "30c36b14-c7d9-44b9-a7cc-d005c741043d", + "record": { + "$ref": "https://inspirehep.net/api/authors/1992536" + }, + "full_name": "Andreev, Yu.M.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Moscow, INR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903030" + } + } + ], + "signature_block": "ANDRAFy", + "raw_affiliations": [ + { + "value": "Institute for Nuclear Research, 117312 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Institute for Nuclear Research,117312 Moscow,Russia" + } + ] + }, + { + "uuid": "56f80aad-7c93-464c-ab7a-e6c74d45c4bf", + "record": { + "$ref": "https://inspirehep.net/api/authors/1034884" + }, + "full_name": "Banerjee, D.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "CERN", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902725" + } + } + ], + "signature_block": "BANARJYd", + "raw_affiliations": [ + { + "value": "CERN, European Organization for Nuclear Research, CH-1211 Geneva, Switzerland", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "CERN,European Organization for Nuclear Research,CH-1211 Geneva,Switzerland" + } + ] + }, + { + "uuid": "e93c74fa-e51c-42ea-a064-b4ee350fc991", + "record": { + "$ref": "https://inspirehep.net/api/authors/1070564" + }, + "full_name": "Bernhard, J.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "CERN", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902725" + } + } + ], + "signature_block": "BARNADj", + "curated_relation": true, + "raw_affiliations": [ + { + "value": "CERN, European Organization for Nuclear Research, CH-1211 Geneva, Switzerland", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "CERN,European Organization for Nuclear Research,CH-1211 Geneva,Switzerland" + } + ] + }, + { + "uuid": "331a0b13-0d52-4ee8-893d-2787357c730b", + "record": { + "$ref": "https://inspirehep.net/api/authors/1876562" + }, + "full_name": "Burtsev, V.E.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Dubna, JINR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902780" + } + } + ], + "signature_block": "BARTSAFv", + "raw_affiliations": [ + { + "value": "Joint Institute for Nuclear Research, 141980 Dubna, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Joint Institute for Nuclear Research,141980 Dubna,Russia" + } + ] + }, + { + "uuid": "99433175-63d7-471d-86f2-7f789db294a6", + "record": { + "$ref": "https://inspirehep.net/api/authors/1599780" + }, + "full_name": "Charitonidis, N.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "CERN", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902725" + } + } + ], + "signature_block": "CARATANADn", + "raw_affiliations": [ + { + "value": "CERN, European Organization for Nuclear Research, CH-1211 Geneva, Switzerland", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "CERN,European Organization for Nuclear Research,CH-1211 Geneva,Switzerland" + } + ] + }, + { + "uuid": "1cadff79-e1e2-4f88-8bcc-0443a335481a", + "record": { + "$ref": "https://inspirehep.net/api/authors/1876563" + }, + "full_name": "Chumakov, A.G.", + "affiliations": [ + { + "value": "Tomsk Pedagogical Inst.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903659" + } + }, + { + "value": "Tomsk Polytechnic U.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903660" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + } + ], + "signature_block": "CANACAVa", + "raw_affiliations": [ + { + "value": "Tomsk State Pedagogical University, 634061 Tomsk, Russia", + "source": "APS" + }, + { + "value": "Tomsk Polytechnic University, 634050 Tomsk, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Tomsk Polytechnic University,634050 Tomsk,Russia" + }, + { + "value": "Tomsk State Pedagogical University,634061 Tomsk,Russia" + } + ] + }, + { + "uuid": "22c878ba-f0ec-4b56-8071-f18143c8fa14", + "record": { + "$ref": "https://inspirehep.net/api/authors/1910368" + }, + "full_name": "Cooke, D.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "University Coll. London", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903311" + } + } + ], + "signature_block": "CACd", + "raw_affiliations": [ + { + "value": "UCL Departement of Physics and Astronomy, University College London, Gower St., London WC1E 6BT, United Kingdom", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "UCL Departement of Physics and Astronomy,University College London,Gower St. London WC1E 6BT,United Kingdom" + } + ] + }, + { + "uuid": "203effd7-1599-4101-997d-1e055be92e48", + "record": { + "$ref": "https://inspirehep.net/api/authors/1045321" + }, + "full_name": "Crivelli, P.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Zurich, ETH", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903369" + } + } + ], + "signature_block": "CRAVALp", + "raw_affiliations": [ + { + "value": "ETH Zürich, Institute for Particle Physics and Astrophysics, CH-8093 Zürich, Switzerland", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "ETH Zürich,Institute for Particle Physics and Astrophysics,CH-8093 Zürich,Switzerland" + } + ] + }, + { + "uuid": "6341f628-81c3-44d3-8479-c7b06b0f35d4", + "record": { + "$ref": "https://inspirehep.net/api/authors/1876560" + }, + "full_name": "Depero, E.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Zurich, ETH", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903369" + } + } + ], + "signature_block": "DAPARe", + "raw_affiliations": [ + { + "value": "ETH Zürich, Institute for Particle Physics and Astrophysics, CH-8093 Zürich, Switzerland", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "ETH Zürich,Institute for Particle Physics and Astrophysics,CH-8093 Zürich,Switzerland" + } + ] + }, + { + "uuid": "e3cc2400-412b-49da-8c0e-1679658413fd", + "record": { + "$ref": "https://inspirehep.net/api/authors/1876565" + }, + "full_name": "Dermenev, A.V.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Moscow, INR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903030" + } + } + ], + "signature_block": "DARNANAFa", + "raw_affiliations": [ + { + "value": "Institute for Nuclear Research, 117312 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Institute for Nuclear Research,117312 Moscow,Russia" + } + ] + }, + { + "uuid": "0fb4f1a6-a102-4e0b-83bd-8ba80e031135", + "record": { + "$ref": "https://inspirehep.net/api/authors/1011541" + }, + "full_name": "Donskov, S.V.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Serpukhov, IHEP", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903194" + } + } + ], + "signature_block": "DANSCAVs", + "raw_affiliations": [ + { + "value": "State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center ’Kurchatov Institute’ (IHEP), 142281 Protvino, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center 'Kurchatov Institute' (IHEP),142281 Protvino,Russia" + } + ] + }, + { + "uuid": "0b1273b5-f1cb-4eb1-b3fa-165551d07362", + "record": { + "$ref": "https://inspirehep.net/api/authors/1391711" + }, + "full_name": "Dusaev, R.R.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Tomsk Polytechnic U.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903660" + } + } + ], + "signature_block": "DASAFr", + "raw_affiliations": [ + { + "value": "Tomsk Polytechnic University, 634050 Tomsk, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Tomsk Polytechnic University,634050 Tomsk,Russia" + } + ] + }, + { + "uuid": "9e4ed6b8-00b8-49fd-936c-0a5a5ad011ae", + "record": { + "$ref": "https://inspirehep.net/api/authors/1607170" + }, + "full_name": "Enik, T.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Dubna, JINR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902780" + } + } + ], + "signature_block": "ENACt", + "curated_relation": true, + "raw_affiliations": [ + { + "value": "Joint Institute for Nuclear Research, 141980 Dubna, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Joint Institute for Nuclear Research,141980 Dubna,Russia" + } + ] + }, + { + "uuid": "f74dec1d-e8d8-423c-852a-ede4ded435d4", + "record": { + "$ref": "https://inspirehep.net/api/authors/1992538" + }, + "full_name": "Feshchenko, A.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Dubna, JINR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902780" + } + } + ], + "signature_block": "FASCANCa", + "raw_affiliations": [ + { + "value": "Joint Institute for Nuclear Research, 141980 Dubna, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Joint Institute for Nuclear Research,141980 Dubna,Russia" + } + ] + }, + { + "uuid": "37bfa584-6c55-47d6-a3ec-93213f299322", + "record": { + "$ref": "https://inspirehep.net/api/authors/1009360" + }, + "full_name": "Frolov, V.N.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Dubna, JINR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902780" + } + } + ], + "signature_block": "FRALAVv", + "raw_affiliations": [ + { + "value": "Joint Institute for Nuclear Research, 141980 Dubna, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Joint Institute for Nuclear Research,141980 Dubna,Russia" + } + ] + }, + { + "uuid": "7a8065a4-8b2d-46d0-9099-98eccb27e6d5", + "record": { + "$ref": "https://inspirehep.net/api/authors/1283538" + }, + "full_name": "Gardikiotis, A.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Patras U.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903743" + } + } + ], + "signature_block": "GARDACATa", + "raw_affiliations": [ + { + "value": "Physics Department, University of Patras, 265 04 Patras, Greece", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Physics Department,University of Patras,265 04 Patras,Greece" + } + ] + }, + { + "uuid": "4554f0da-1233-4758-97af-c3204ec6210a", + "record": { + "$ref": "https://inspirehep.net/api/authors/1008577" + }, + "full_name": "Gerassimov, S.G.", + "affiliations": [ + { + "value": "TUM-IAS, Munich", + "record": { + "$ref": "https://inspirehep.net/api/institutions/911544" + } + }, + { + "value": "Lebedev Inst.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902955" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Munich, Tech. U.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903037" + } + }, + { + "value": "LPI, Moscow (main)", + "record": { + "$ref": "https://inspirehep.net/api/institutions/1256696" + } + } + ], + "signature_block": "GARASANAVs", + "raw_affiliations": [ + { + "value": "Technische Universität München, Physik Department, 85748 Garching, Germany", + "source": "APS" + }, + { + "value": "P.N. Lebedev Physical Institute of the Russian Academy of Sciences, 119 991 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Technische Universität München,Physik Department,85748 Garching,Germany" + }, + { + "value": "P.N. Lebedev Physical Institute,Moscow,Russia,119 991 Moscow,Russia" + } + ] + }, + { + "uuid": "00e5f85a-8aa0-45ef-9487-8bf617e55089", + "record": { + "$ref": "https://inspirehep.net/api/authors/1008194" + }, + "full_name": "Gninenko, S.N.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Moscow, INR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903030" + } + } + ], + "signature_block": "GNANANCs", + "raw_affiliations": [ + { + "value": "Institute for Nuclear Research, 117312 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Institute for Nuclear Research,117312 Moscow,Russia" + } + ] + }, + { + "uuid": "1e297ef8-0117-4f7d-83a2-d27add76e4e6", + "record": { + "$ref": "https://inspirehep.net/api/authors/1876567" + }, + "full_name": "Hösgen, M.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Bonn U., HISKP", + "record": { + "$ref": "https://inspirehep.net/api/institutions/908572" + } + } + ], + "signature_block": "HASGANm", + "raw_affiliations": [ + { + "value": "Universität Bonn, Helmholtz-Institut für Strahlen-und Kernphysik, 53115 Bonn, Germany", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Universität Bonn,Helmholtz-Institut für Strahlen-und Kernphysik,53115 Bonn,Germany" + } + ] + }, + { + "uuid": "e9b6b061-30d1-4fd9-b5c0-4981f7482b20", + "record": { + "$ref": "https://inspirehep.net/api/authors/1639537" + }, + "full_name": "Jeckel, M.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "CERN", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902725" + } + } + ], + "signature_block": "JACALm", + "raw_affiliations": [ + { + "value": "CERN, European Organization for Nuclear Research, CH-1211 Geneva, Switzerland", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "CERN,European Organization for Nuclear Research,CH-1211 Geneva,Switzerland" + } + ] + }, + { + "uuid": "177ed12a-e59b-4f43-b456-31b20d34f7db", + "record": { + "$ref": "https://inspirehep.net/api/authors/1050796" + }, + "full_name": "Kachanov, V.A.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Serpukhov, IHEP", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903194" + } + } + ], + "signature_block": "CACANAVv", + "raw_affiliations": [ + { + "value": "State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center ’Kurchatov Institute’ (IHEP), 142281 Protvino, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center 'Kurchatov Institute' (IHEP),142281 Protvino,Russia" + } + ] + }, + { + "uuid": "1a007fb2-f57c-45ed-b618-41323d58c0e8", + "record": { + "$ref": "https://inspirehep.net/api/authors/1876568" + }, + "full_name": "Karneyeu, A.E.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Moscow, INR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903030" + } + } + ], + "signature_block": "CARNYa", + "raw_affiliations": [ + { + "value": "Institute for Nuclear Research, 117312 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Institute for Nuclear Research,117312 Moscow,Russia" + } + ] + }, + { + "uuid": "73f27656-2265-4b0a-9686-a064f4d8a6e1", + "record": { + "$ref": "https://inspirehep.net/api/authors/1062433" + }, + "full_name": "Kekelidze, G.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Dubna, JINR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902780" + } + } + ], + "signature_block": "CACALADSg", + "raw_affiliations": [ + { + "value": "Joint Institute for Nuclear Research, 141980 Dubna, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Joint Institute for Nuclear Research,141980 Dubna,Russia" + } + ] + }, + { + "uuid": "47bfbbcb-61a4-4eec-9e5e-812db0057706", + "record": { + "$ref": "https://inspirehep.net/api/authors/1003144" + }, + "full_name": "Ketzer, B.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Bonn U., HISKP", + "record": { + "$ref": "https://inspirehep.net/api/institutions/908572" + } + } + ], + "signature_block": "CATSARb", + "raw_affiliations": [ + { + "value": "Universität Bonn, Helmholtz-Institut für Strahlen-und Kernphysik, 53115 Bonn, Germany", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Universität Bonn,Helmholtz-Institut für Strahlen-und Kernphysik,53115 Bonn,Germany" + } + ] + }, + { + "uuid": "8621869e-bfa4-4ae8-9786-ffa8e52c0ba0", + "record": { + "$ref": "https://inspirehep.net/api/authors/1074204" + }, + "full_name": "Kirpichnikov, D.V.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Moscow, INR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903030" + } + } + ], + "signature_block": "CARPACHNACAVd", + "raw_affiliations": [ + { + "value": "Institute for Nuclear Research, 117312 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Institute for Nuclear Research,117312 Moscow,Russia" + } + ] + }, + { + "uuid": "e36b4781-4ed0-4c0c-afdb-c33b8ac547cf", + "record": { + "$ref": "https://inspirehep.net/api/authors/1002797" + }, + "full_name": "Kirsanov, M.M.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Moscow, INR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903030" + } + } + ], + "signature_block": "CARSANAVm", + "raw_affiliations": [ + { + "value": "Institute for Nuclear Research, 117312 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Institute for Nuclear Research,117312 Moscow,Russia" + } + ] + }, + { + "uuid": "427de10c-cbbd-444a-95cc-cc8378bcdd66", + "record": { + "$ref": "https://inspirehep.net/api/authors/1907803" + }, + "full_name": "Kolosov, V.N.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Serpukhov, IHEP", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903194" + } + } + ], + "signature_block": "CALASAVv", + "raw_affiliations": [ + { + "value": "State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center ’Kurchatov Institute’ (IHEP), 142281 Protvino, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center 'Kurchatov Institute' (IHEP),142281 Protvino,Russia" + } + ] + }, + { + "uuid": "8b0e9272-9831-4ed0-b94e-f74ba620817c", + "record": { + "$ref": "https://inspirehep.net/api/authors/1002227" + }, + "full_name": "Konorov, I.V.", + "affiliations": [ + { + "value": "TUM-IAS, Munich", + "record": { + "$ref": "https://inspirehep.net/api/institutions/911544" + } + }, + { + "value": "Lebedev Inst.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902955" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Munich, Tech. U.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903037" + } + }, + { + "value": "LPI, Moscow (main)", + "record": { + "$ref": "https://inspirehep.net/api/institutions/1256696" + } + } + ], + "signature_block": "CANARAVi", + "raw_affiliations": [ + { + "value": "Technische Universität München, Physik Department, 85748 Garching, Germany", + "source": "APS" + }, + { + "value": "P.N. Lebedev Physical Institute of the Russian Academy of Sciences, 119 991 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Technische Universität München,Physik Department,85748 Garching,Germany" + }, + { + "value": "P.N. Lebedev Physical Institute,Moscow,Russia,119 991 Moscow,Russia" + } + ] + }, + { + "uuid": "2cffe3e3-8b61-41f1-89b5-b3faa7002053", + "record": { + "$ref": "https://inspirehep.net/api/authors/1001998" + }, + "full_name": "Kovalenko, S.G.", + "affiliations": [ + { + "value": "Chile U., Santiago", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902731" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Andres Bello Natl. U.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907969" + } + } + ], + "signature_block": "CAVALANCs", + "raw_affiliations": [ + { + "value": "Millennium Institute for Subatomic Physics at the High-Energy Frontier (SAPHIR) of ANID, Fernández Concha 700, Santiago, Chile", + "source": "APS" + }, + { + "value": "Departamento de Ciencias Físicas, Universidad Andres Bello, Sazié 2212, Piso 7, Santiago, Chile", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Departamento de Ciencias Físicas,Universidad Andres Bello,Sazié2212,Piso 7,Santiago,Chile" + } + ] + }, + { + "uuid": "ff26c423-6958-4612-a3a9-a0c38cc09d79", + "record": { + "$ref": "https://inspirehep.net/api/authors/1001923" + }, + "full_name": "Kramarenko, V.A.", + "affiliations": [ + { + "value": "SINP, Moscow", + "record": { + "$ref": "https://inspirehep.net/api/institutions/908795" + } + }, + { + "value": "Dubna, JINR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902780" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + } + ], + "signature_block": "CRANARANCv", + "raw_affiliations": [ + { + "value": "Skobeltsyn Institute of Nuclear Physics, Lomonosov Moscow State University, 119991 Moscow, Russia", + "source": "APS" + }, + { + "value": "Joint Institute for Nuclear Research, 141980 Dubna, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Joint Institute for Nuclear Research,141980 Dubna,Russia" + }, + { + "value": "Skobeltsyn Institute of Nuclear Physics,Lomonosov Moscow State University,119991 Moscow,Russia" + } + ] + }, + { + "uuid": "f84024e3-1b0f-4fd7-a703-e8f3a58e3aaa", + "record": { + "$ref": "https://inspirehep.net/api/authors/1070742" + }, + "full_name": "Kravchuk, L.V.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Moscow, INR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903030" + } + } + ], + "signature_block": "CRAVCACl", + "raw_affiliations": [ + { + "value": "Institute for Nuclear Research, 117312 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Institute for Nuclear Research,117312 Moscow,Russia" + } + ] + }, + { + "uuid": "06850883-3395-4644-b2b6-bdc2927afd34", + "record": { + "$ref": "https://inspirehep.net/api/authors/1001900" + }, + "full_name": "Krasnikov, N.V.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Dubna, JINR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902780" + } + }, + { + "value": "Moscow, INR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903030" + } + } + ], + "signature_block": "CRASNACAVn", + "raw_affiliations": [ + { + "value": "Joint Institute for Nuclear Research, 141980 Dubna, Russia", + "source": "APS" + }, + { + "value": "Institute for Nuclear Research, 117312 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Joint Institute for Nuclear Research,141980 Dubna,Russia" + }, + { + "value": "Institute for Nuclear Research,117312 Moscow,Russia" + } + ] + }, + { + "uuid": "19eb1116-242c-498a-bdb5-704535e1f396", + "record": { + "$ref": "https://inspirehep.net/api/authors/1001595" + }, + "full_name": "Kuleshov, S.V.", + "affiliations": [ + { + "value": "Chile U., Santiago", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902731" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Andres Bello Natl. U.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907969" + } + } + ], + "signature_block": "CALASAVs", + "raw_affiliations": [ + { + "value": "Millennium Institute for Subatomic Physics at the High-Energy Frontier (SAPHIR) of ANID, Fernández Concha 700, Santiago, Chile", + "source": "APS" + }, + { + "value": "Departamento de Ciencias Físicas, Universidad Andres Bello, Sazié 2212, Piso 7, Santiago, Chile", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Departamento de Ciencias Físicas,Universidad Andres Bello,Sazié2212,Piso 7,Santiago,Chile" + } + ] + }, + { + "uuid": "ae331996-d6fc-49d7-a727-8705a87632af", + "record": { + "$ref": "https://inspirehep.net/api/authors/999453" + }, + "full_name": "Lyubovitskij, V.E.", + "affiliations": [ + { + "value": "Santa Maria U., Valparaiso", + "record": { + "$ref": "https://inspirehep.net/api/institutions/904589" + } + }, + { + "value": "Chile U., Santiago", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902731" + } + }, + { + "value": "Tomsk Pedagogical Inst.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903659" + } + }, + { + "value": "Tomsk Polytechnic U.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903660" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "CCTVal, Valparaiso", + "record": { + "$ref": "https://inspirehep.net/api/institutions/911929" + } + } + ], + "signature_block": "LABAVATSCAJv", + "curated_relation": true, + "raw_affiliations": [ + { + "value": "Universidad Técnica Federico Santa María, Centro Científica y Tecnológica de Valparaíso-CCTVal, 2390123 Valparaíso, Chile", + "source": "APS" + }, + { + "value": "Millennium Institute for Subatomic Physics at the High-Energy Frontier (SAPHIR) of ANID, Fernández Concha 700, Santiago, Chile", + "source": "APS" + }, + { + "value": "Tomsk State Pedagogical University, 634061 Tomsk, Russia", + "source": "APS" + }, + { + "value": "Tomsk Polytechnic University, 634050 Tomsk, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Tomsk Polytechnic University,634050 Tomsk,Russia" + }, + { + "value": "Tomsk State Pedagogical University,634061 Tomsk,Russia" + }, + { + "value": "Universidad Técnica Federico Santa María,2390123 Valparaíso,Chile" + } + ] + }, + { + "uuid": "cf10fe18-f156-4f61-a91e-d2b02868b44e", + "record": { + "$ref": "https://inspirehep.net/api/authors/1992539" + }, + "full_name": "Lysan, V.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Dubna, JINR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902780" + } + } + ], + "signature_block": "LASANv", + "raw_affiliations": [ + { + "value": "Joint Institute for Nuclear Research, 141980 Dubna, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Joint Institute for Nuclear Research,141980 Dubna,Russia" + } + ] + }, + { + "uuid": "2532affc-4ab4-4c61-a87d-ffe3ff1ff260", + "record": { + "$ref": "https://inspirehep.net/api/authors/998249" + }, + "full_name": "Matveev, V.A.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Dubna, JINR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902780" + } + } + ], + "signature_block": "MATVAFv", + "raw_affiliations": [ + { + "value": "Joint Institute for Nuclear Research, 141980 Dubna, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Joint Institute for Nuclear Research,141980 Dubna,Russia" + } + ] + }, + { + "uuid": "66107d8e-1a77-48a3-81cb-65abc12a2419", + "record": { + "$ref": "https://inspirehep.net/api/authors/1043529" + }, + "full_name": "Mikhailov, Yu.V.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Serpukhov, IHEP", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903194" + } + } + ], + "signature_block": "MACALAVy", + "raw_affiliations": [ + { + "value": "State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center ’Kurchatov Institute’ (IHEP), 142281 Protvino, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center 'Kurchatov Institute' (IHEP),142281 Protvino,Russia" + } + ] + }, + { + "uuid": "b5e018b8-a003-4c25-b002-721b9064ec1e", + "record": { + "$ref": "https://inspirehep.net/api/authors/1121788" + }, + "full_name": "Molina Bueno, L.", + "affiliations": [ + { + "value": "Zurich, ETH", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903369" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + } + ], + "signature_block": "BANl", + "raw_affiliations": [ + { + "value": "ETH Zürich, Institute for Particle Physics and Astrophysics, CH-8093 Zürich, Switzerland", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV), Carrer del Catedrtic Jos Beltrn Martinez, 2, 46980 Paterna, Valencia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "ETH Zürich,Institute for Particle Physics and Astrophysics,CH-8093 Zürich,Switzerland" + } + ] + }, + { + "uuid": "5c4fac53-ebc8-4e7f-990d-08faac1640ca", + "record": { + "$ref": "https://inspirehep.net/api/authors/1992540" + }, + "full_name": "Peshekhonov, D.V.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Dubna, JINR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902780" + } + } + ], + "signature_block": "PASACANAVd", + "raw_affiliations": [ + { + "value": "Joint Institute for Nuclear Research, 141980 Dubna, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Joint Institute for Nuclear Research,141980 Dubna,Russia" + } + ] + }, + { + "uuid": "a24812ae-eb43-48b0-90c1-91d07dbc4977", + "record": { + "$ref": "https://inspirehep.net/api/authors/1898990" + }, + "full_name": "Polyakov, V.A.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Serpukhov, IHEP", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903194" + } + } + ], + "signature_block": "PALACAVv", + "raw_affiliations": [ + { + "value": "State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center ’Kurchatov Institute’ (IHEP), 142281 Protvino, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center 'Kurchatov Institute' (IHEP),142281 Protvino,Russia" + } + ] + }, + { + "uuid": "85040e98-9eb0-4eb7-adf2-4f9224a05690", + "record": { + "$ref": "https://inspirehep.net/api/authors/1026910" + }, + "full_name": "Radics, B.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Zurich, ETH", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903369" + } + } + ], + "signature_block": "RADACb", + "raw_affiliations": [ + { + "value": "ETH Zürich, Institute for Particle Physics and Astrophysics, CH-8093 Zürich, Switzerland", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "ETH Zürich,Institute for Particle Physics and Astrophysics,CH-8093 Zürich,Switzerland" + } + ] + }, + { + "uuid": "bf9cb194-0a3d-4686-a0bb-94442c9ddf08", + "record": { + "$ref": "https://inspirehep.net/api/authors/2128027" + }, + "full_name": "Rojas, R.", + "affiliations": [ + { + "value": "Santa Maria U., Valparaiso", + "record": { + "$ref": "https://inspirehep.net/api/institutions/904589" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "CCTVal, Valparaiso", + "record": { + "$ref": "https://inspirehep.net/api/institutions/911929" + } + } + ], + "signature_block": "RAJr", + "raw_affiliations": [ + { + "value": "Universidad Técnica Federico Santa María, Centro Científica y Tecnológica de Valparaíso-CCTVal, 2390123 Valparaíso, Chile", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Universidad Técnica Federico Santa María,2390123 Valparaíso,Chile" + } + ] + }, + { + "uuid": "6b2426a2-32af-4f8c-ad88-fd5601845e5b", + "record": { + "$ref": "https://inspirehep.net/api/authors/991041" + }, + "full_name": "Rubbia, A.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Zurich, ETH", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903369" + } + } + ], + "signature_block": "RABa", + "raw_affiliations": [ + { + "value": "ETH Zürich, Institute for Particle Physics and Astrophysics, CH-8093 Zürich, Switzerland", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "ETH Zürich,Institute for Particle Physics and Astrophysics,CH-8093 Zürich,Switzerland" + } + ] + }, + { + "uuid": "bb8980b3-1240-486d-885d-ee6a7d390874", + "record": { + "$ref": "https://inspirehep.net/api/authors/990489" + }, + "full_name": "Samoylenko, V.D.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Serpukhov, IHEP", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903194" + } + } + ], + "signature_block": "SANYLANCv", + "raw_affiliations": [ + { + "value": "State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center ’Kurchatov Institute’ (IHEP), 142281 Protvino, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center 'Kurchatov Institute' (IHEP),142281 Protvino,Russia" + } + ] + }, + { + "uuid": "be9589ea-a862-4567-b9fd-08a8cbcce30e", + "record": { + "$ref": "https://inspirehep.net/api/authors/1876572" + }, + "full_name": "Sieber, H.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Zurich, ETH", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903369" + } + } + ], + "signature_block": "SABARh", + "raw_affiliations": [ + { + "value": "ETH Zürich, Institute for Particle Physics and Astrophysics, CH-8093 Zürich, Switzerland", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "ETH Zürich,Institute for Particle Physics and Astrophysics,CH-8093 Zürich,Switzerland" + } + ] + }, + { + "uuid": "bee6965d-9798-4c14-a470-21d1a2957fcb", + "record": { + "$ref": "https://inspirehep.net/api/authors/1074204" + }, + "full_name": "Shchukin, D.", + "affiliations": [ + { + "value": "Lebedev Inst.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902955" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "LPI, Moscow (main)", + "record": { + "$ref": "https://inspirehep.net/api/institutions/1256696" + } + } + ], + "signature_block": "SCACANd", + "raw_affiliations": [ + { + "value": "P.N. Lebedev Physical Institute of the Russian Academy of Sciences, 119 991 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "P.N. Lebedev Physical Institute,Moscow,Russia,119 991 Moscow,Russia" + } + ] + }, + { + "uuid": "a5721dad-1661-4b21-8e37-f86ff884ba07", + "record": { + "$ref": "https://inspirehep.net/api/authors/1062381" + }, + "full_name": "Tikhomirov, V.O.", + "affiliations": [ + { + "value": "Lebedev Inst.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902955" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "LPI, Moscow (main)", + "record": { + "$ref": "https://inspirehep.net/api/institutions/1256696" + } + } + ], + "signature_block": "TACANARAVv", + "curated_relation": true, + "raw_affiliations": [ + { + "value": "P.N. Lebedev Physical Institute of the Russian Academy of Sciences, 119 991 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "P.N. Lebedev Physical Institute,Moscow,Russia,119 991 Moscow,Russia" + } + ] + }, + { + "uuid": "ef1ef9e0-9627-4660-9764-b5e6a582ed37", + "record": { + "$ref": "https://inspirehep.net/api/authors/1876573" + }, + "full_name": "Tlisova, I.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Moscow, INR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903030" + } + } + ], + "signature_block": "TLASAVi", + "raw_affiliations": [ + { + "value": "Institute for Nuclear Research, 117312 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Institute for Nuclear Research,117312 Moscow,Russia" + } + ] + }, + { + "uuid": "ab8b1910-b100-4cca-a0f9-d892f29dad6d", + "record": { + "$ref": "https://inspirehep.net/api/authors/1039258" + }, + "full_name": "Toropin, A.N.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Moscow, INR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903030" + } + } + ], + "signature_block": "TARAPANa", + "raw_affiliations": [ + { + "value": "Institute for Nuclear Research, 117312 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Institute for Nuclear Research,117312 Moscow,Russia" + } + ] + }, + { + "uuid": "d795e71b-24cc-4002-9f3c-ebc4abe1be03", + "record": { + "$ref": "https://inspirehep.net/api/authors/1992552" + }, + "full_name": "Trifonov, A.Yu.", + "affiliations": [ + { + "value": "Tomsk Pedagogical Inst.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903659" + } + }, + { + "value": "Tomsk Polytechnic U.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903660" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + } + ], + "signature_block": "TRAFANAVa", + "raw_affiliations": [ + { + "value": "Tomsk State Pedagogical University, 634061 Tomsk, Russia", + "source": "APS" + }, + { + "value": "Tomsk Polytechnic University, 634050 Tomsk, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Tomsk Polytechnic University,634050 Tomsk,Russia" + }, + { + "value": "Tomsk State Pedagogical University,634061 Tomsk,Russia" + } + ] + }, + { + "uuid": "5b193a05-0a8c-4b43-b35e-1032f5280763", + "record": { + "$ref": "https://inspirehep.net/api/authors/1876575" + }, + "full_name": "Vasilishin, B.I.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Tomsk Polytechnic U.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903660" + } + } + ], + "signature_block": "VASALASANb", + "raw_affiliations": [ + { + "value": "Tomsk Polytechnic University, 634050 Tomsk, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Tomsk Polytechnic University,634050 Tomsk,Russia" + } + ] + }, + { + "uuid": "6ef1a9c5-63bb-4d69-9392-2cc856b603ff", + "record": { + "$ref": "https://inspirehep.net/api/authors/1910374" + }, + "full_name": "Vasquez Arenas, G.", + "affiliations": [ + { + "value": "Santa Maria U., Valparaiso", + "record": { + "$ref": "https://inspirehep.net/api/institutions/904589" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "CCTVal, Valparaiso", + "record": { + "$ref": "https://inspirehep.net/api/institutions/911929" + } + } + ], + "signature_block": "ARANg", + "raw_affiliations": [ + { + "value": "Universidad Técnica Federico Santa María, Centro Científica y Tecnológica de Valparaíso-CCTVal, 2390123 Valparaíso, Chile", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Universidad Técnica Federico Santa María,2390123 Valparaíso,Chile" + } + ] + }, + { + "uuid": "e53204e3-6e01-4090-8d0b-28c6e4631fce", + "record": { + "$ref": "https://inspirehep.net/api/authors/1876577" + }, + "full_name": "Volkov, P.V.", + "affiliations": [ + { + "value": "SINP, Moscow", + "record": { + "$ref": "https://inspirehep.net/api/institutions/908795" + } + }, + { + "value": "Dubna, JINR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902780" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + } + ], + "signature_block": "VALCAVp", + "raw_affiliations": [ + { + "value": "Skobeltsyn Institute of Nuclear Physics, Lomonosov Moscow State University, 119991 Moscow, Russia", + "source": "APS" + }, + { + "value": "Joint Institute for Nuclear Research, 141980 Dubna, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Joint Institute for Nuclear Research,141980 Dubna,Russia" + }, + { + "value": "Skobeltsyn Institute of Nuclear Physics,Lomonosov Moscow State University,119991 Moscow,Russia" + } + ] + }, + { + "uuid": "8b274e78-4aad-401b-bcfb-be652b05b914", + "record": { + "$ref": "https://inspirehep.net/api/authors/1992553" + }, + "full_name": "Volkov, V.Yu.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "SINP, Moscow", + "record": { + "$ref": "https://inspirehep.net/api/institutions/908795" + } + } + ], + "signature_block": "VALCAVv", + "raw_affiliations": [ + { + "value": "Skobeltsyn Institute of Nuclear Physics, Lomonosov Moscow State University, 119991 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Skobeltsyn Institute of Nuclear Physics,Lomonosov Moscow State University,119991 Moscow,Russia" + } + ] + }, + { + "uuid": "d2841447-e76f-485a-8a67-20c5f7636dc0", + "record": { + "$ref": "https://inspirehep.net/api/authors/1478928" + }, + "full_name": "Ulloa, P.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Andres Bello Natl. U.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907969" + } + } + ], + "signature_block": "ULp", + "curated_relation": true, + "raw_affiliations": [ + { + "value": "Departamento de Ciencias Físicas, Universidad Andres Bello, Sazié 2212, Piso 7, Santiago, Chile", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Departamento de Ciencias Físicas,Universidad Andres Bello,Sazié2212,Piso 7,Santiago,Chile" + } + ] + } + ], + "curated": true, + "figures": [ + { + "key": "1fa3b8447ad01f97a1956271a2479331", + "url": "https://inspirehep.net/files/1fa3b8447ad01f97a1956271a2479331", + "label": "fig:setup", + "source": "arxiv", + "caption": "The NA64 setup to search for $A'(a)\\to \\ee$ decays of the bremsstrahlung $A'(a)$ produced in the reaction $eZ \\to eZA'(a) $ of the 150 GeV electrons incident on the active WCAL target. The figure is reproduced from Ref. \\cite{visible-2018-analysis}.", + "filename": "setup_2018_vis.png", + "material": "preprint" + }, + { + "key": "ca453d4bc2b5979850ea4a5fca1943a6", + "url": "https://inspirehep.net/files/ca453d4bc2b5979850ea4a5fca1943a6", + "label": "fig:result", + "source": "arxiv", + "caption": "The 90\\% C.L. limits on the pseudoscalar particles decaying to $e^+e^-$ pairs. On the right vertical axis we use the standard notation for the pseudo-scalar coupling $\\xi_e=\\epsilon (V/m_e) \\sqrt{4 \\pi \\alpha_{QED}}$, where $V=246$~GeV is a vacuum expectation value of the Higgs field \\cite{Andreas:2010ms}. This corresponds to the Lagrangian term $\\mathcal{L}\\supset -i \\xi_e \\frac{m_e}{V} a \\bar{\\psi}_e \\gamma_5 \\psi_e$. The red vertical line corresponds to the ATOMKI anomaly at $m_a = 16.7$ MeV (central value of the first result on berillium). The $\\epsilon$ range excluded at this mass is $2.1\\times 10^{-4} < \\epsilon < 3.2\\times 10^{-4}$. The region excluded using only the data collected with the visible mode geometry is denoted as \"NA64 vis.\", the extention of this region obtained using all data is denoted as \"NA64 invis.\". The regions excluded by the $(g-2)_e$ measurements (Berkley \\cite{Parker191} and LKB \\cite{finestructure2020}) are shown. The limits from the electron beam-dump experiments E774~\\cite{bross} and Orsay~\\cite{dav} are taken from Ref.~\\cite{Andreas:2010ms}.", + "filename": "X_linear_Kirpich_visible_pseudoscalar.png", + "material": "preprint" + } + ], + "license": [ + { + "url": "https://creativecommons.org/licenses/by/4.0/", + "license": "CC BY 4.0", + "material": "publication" + }, + { + "url": "http://creativecommons.org/licenses/by/4.0/", + "license": "CC BY 4.0", + "material": "preprint" + } + ], + "texkeys": [ + "NA64:2021aiq", + "NA64:2021ked", + "Andreev:2021syk" + ], + "citeable": true, + "imprints": [ + { + "date": "2021-12-01" + } + ], + "keywords": [ + { + "value": "electron: beam", + "schema": "INSPIRE" + }, + { + "value": "new physics: search for", + "schema": "INSPIRE" + }, + { + "value": "new particle", + "schema": "INSPIRE" + }, + { + "value": "cross section: difference", + "schema": "INSPIRE" + }, + { + "value": "dimension: 2", + "schema": "INSPIRE" + }, + { + "value": "nucleus: transition", + "schema": "INSPIRE" + }, + { + "value": "statistics", + "schema": "INSPIRE" + }, + { + "value": "anomaly", + "schema": "INSPIRE" + }, + { + "value": "pseudoscalar particle: leptonic decay", + "schema": "INSPIRE" + }, + { + "value": "electron: pair production", + "schema": "INSPIRE" + }, + { + "value": "electron: coupling", + "schema": "INSPIRE" + }, + { + "value": "pseudoscalar particle: coupling", + "schema": "INSPIRE" + }, + { + "value": "pseudoscalar particle: mass", + "schema": "INSPIRE" + }, + { + "value": "pseudoscalar", + "schema": "INSPIRE" + }, + { + "value": "CERN SPS", + "schema": "INSPIRE" + }, + { + "value": "vector boson: mass", + "schema": "INSPIRE" + }, + { + "value": "accelerator", + "schema": "INSPIRE" + }, + { + "value": "efficiency", + "schema": "INSPIRE" + }, + { + "value": "axion-like particles", + "schema": "INSPIRE" + }, + { + "value": "sensitivity", + "schema": "INSPIRE" + }, + { + "value": "statistical analysis", + "schema": "INSPIRE" + }, + { + "value": "parameter space", + "schema": "INSPIRE" + }, + { + "value": "background", + "schema": "INSPIRE" + }, + { + "value": "lifetime: difference", + "schema": "INSPIRE" + }, + { + "value": "beryllium", + "schema": "INSPIRE" + }, + { + "value": "experimental results", + "schema": "INSPIRE" + }, + { + "value": "talk: Prague 2020/07/30", + "schema": "INSPIRE" + }, + { + "value": "dark matter: direct production", + "schema": "INSPIRE" + }, + { + "value": "gauge boson: postulated particle", + "schema": "INSPIRE" + }, + { + "value": "gauge boson: leptonic decay", + "schema": "INSPIRE" + }, + { + "value": "pseudoscalar particle", + "schema": "INSPIRE" + }, + { + "value": "data analysis method", + "schema": "INSPIRE" + }, + { + "value": "S029AXE", + "schema": "PDG" + } + ], + "refereed": true, + "abstracts": [ + { + "value": "We report the results of a search for a light pseudoscalar particle a that couples to electrons and decays to e+e− performed using the high-energy CERN SPS H4 electron beam. If such light pseudoscalar exists, it could explain the ATOMKI anomaly (an excess of e+e− pairs in the nuclear transitions of 8Be and 4He nuclei at the invariant mass ≃17  MeV observed by the experiment at the 5 MV Van de Graaff accelerator at ATOMKI, Hungary). We used the NA64 data collected in the “visible mode” configuration with a total statistics corresponding to 8.4×1010 electrons on target (EOT) in 2017 and 2018. In order to increase sensitivity to small coupling parameter ε we also used the data collected in 2016–2018 in the “invisible mode” configuration of NA64 with a total statistics corresponding to 2.84×1011 EOT. The background and efficiency estimates for these two configurations were retained from our previous analyses searching for light vector bosons and axionlike particles (ALP) (the latter were assumed to couple predominantly to γ). In this work we recalculate the signal yields, which are different due to different cross section and lifetime of a pseudoscalar particle a, and perform a new statistical analysis. As a result, the region of the two dimensional parameter space ma−ε in the mass range from 1 to 17.1 MeV is excluded. At the mass of the central value of the ATOMKI anomaly (the first result obtained on the beryllium nucleus, 16.7 MeV) the values of ε in the range 2.1×10−4<ε<3.2×10−4 are excluded.", + "source": "APS" + }, + { + "value": "We report the results of a search for a light pseudoscalar particle $a$ that couples to electrons and decays to $e^+e^-$ performed using the high-energy CERN SPS H4 electron beam. If such pseudoscalar with a mass $\\simeq 17$ MeV exists, it could explain the ATOMKI anomaly. We used the NA64 data samples collected in the \"visible mode\" configuration with total statistics corresponding to $8.4\\times 10^{10}$ electrons on target (EOT) in 2017 and 2018. In order to increase sensitivity to small coupling parameter $\\epsilon$ we used also the data collected in 2016-2018 in the \"invisible mode\" configuration of NA64 with a total statistics corresponding to $2.84\\times 10^{11}$ EOT. A thorough analysis of both these data samples in the sense of background and efficiency estimations was already performed and reported in our previous papers devoted to the search for light vector particles and axion-like particles (ALP). In this work we recalculate the signal yields, which are different due to different cross section and life time of a pseudoscalar particle $a$, and perform a new statistical analysis. As a result, the region of the two dimensional parameter space $m_a - \\epsilon$ in the mass range from 1 to 17.1 MeV is excluded. At the mass of the ATOMKI anomaly the values of $\\epsilon$ in the range $2.1 \\times 10^{-4} < \\epsilon < 3.2 \\times 10^{-4}$ are excluded.", + "source": "arXiv" + } + ], + "copyright": [ + { + "year": 2021, + "holder": "authors", + "material": "publication" + } + ], + "public_notes": [ + { + "value": "6 pages, 2 figures", + "source": "arXiv" + } + ], + "arxiv_eprints": [ + { + "value": "2104.13342", + "categories": [ + "hep-ex" + ] + } + ], + "document_type": [ + "article", + "conference paper" + ], + "preprint_date": "2021-04-27", + "collaborations": [ + { + "value": "NA64", + "record": { + "$ref": "https://inspirehep.net/api/experiments/1479798" + } + } + ], + "control_number": 1992535, + "legacy_version": "20210526164834.0", + "deleted_records": [ + { + "$ref": "https://inspirehep.net/api/literature/1860981" + } + ], + "number_of_pages": 5, + "inspire_categories": [ + { + "term": "Experiment-HEP", + "source": "curator" + }, + { + "term": "Experiment-HEP", + "source": "arxiv" + }, + { + "term": "Experiment-HEP" + } + ], + "legacy_creation_date": "2021-04-28", + "accelerator_experiments": [ + { + "record": { + "$ref": "https://inspirehep.net/api/experiments/1479798" + }, + "legacy_name": "CERN-NA-064" + } + ], + "external_system_identifiers": [ + { + "value": "2765541", + "schema": "CDS" + }, + { + "value": "2798711", + "schema": "CDS" + } + ] + }, + "uuid": "efbeb951-5ef3-4399-8d9a-0a2ce7a5e8c5", + "created": "2021-04-28T00:00:00+00:00" + } + ] + } +} \ No newline at end of file diff --git a/site/tests/inspire_harvester/test_update_create_logic.py b/site/tests/inspire_harvester/test_update_create_logic.py index 79f9b62a..64deff6c 100644 --- a/site/tests/inspire_harvester/test_update_create_logic.py +++ b/site/tests/inspire_harvester/test_update_create_logic.py @@ -1,24 +1,28 @@ import json from functools import partial +from time import sleep from unittest.mock import Mock, patch from celery import current_app from invenio_access.permissions import system_identity from invenio_rdm_records.proxies import current_rdm_records_service from invenio_rdm_records.records import RDMRecord +from invenio_search.engine import dsl from invenio_vocabularies.services.tasks import process_datastream +from cds_rdm.legacy.resolver import get_record_by_version + from .utils import mock_requests_get, run_harvester_mock def test_new_non_CDS_record( - running_app, location, scientific_community, datastream_config + running_app, location, scientific_community, datastream_config ): """Test new non-CDS origin record.""" with open( - "tests/inspire_harvester/data/completely_new_inspire_rec.json", - "r", + "tests/inspire_harvester/data/completely_new_inspire_rec.json", + "r", ) as f: new_record = json.load(f) @@ -57,23 +61,123 @@ def test_new_non_CDS_record( } -def test_CDS_DOI_record_not_found(running_app, location, scientific_community): +def test_CDS_DOI_create_record_fails( + running_app, location, scientific_community, datastream_config +): """Test insert record with CDS DOI - no record matched (deleted?).""" - passed = False + with open( + "tests/inspire_harvester/data/record_with_cds_DOI.json", + "r", + ) as f: + new_record = json.load(f) + mock_record = partial(mock_requests_get, mock_content=new_record) + run_harvester_mock(datastream_config, mock_record) + RDMRecord.index.refresh() -def test_update_record_with_CDS_DOI(running_app, location, scientific_community): - """Test update record with CDS DOI - no record matched.""" - passed = False + doi_filters = [ + dsl.Q("term", **{"pids.doi": "10.17181/CERN.LELX.5VJY"}), + ] + filter = dsl.Q("bool", filter=doi_filters) + + created_records = current_rdm_records_service.search( + system_identity, extra_filter=filter + ) + assert created_records.total == 0 + + +def test_update_record_with_CDS_DOI_one_doc_type( + running_app, location, scientific_community, minimal_record, datastream_config +): + """Test update record with CDS DOI - matched record. + + Should create new version of record with article resource type. + """ + + # set this to emulate DOI creation + running_app.app.config["RDM_PERSISTENT_IDENTIFIERS"]["doi"]["required"] = True + draft = current_rdm_records_service.create(system_identity, minimal_record) + record = current_rdm_records_service.publish(system_identity, draft.id) + + with open( + "tests/inspire_harvester/data/record_with_cds_DOI.json", + "r", + ) as f: + new_record = json.load(f) + new_record["hits"]["hits"][0]["metadata"]["dois"] = [ + {"value": record["pids"]["doi"]["identifier"]} + ] + + mock_record = partial(mock_requests_get, mock_content=new_record) + RDMRecord.index.refresh() + run_harvester_mock(datastream_config, mock_record) + RDMRecord.index.refresh() + + doi_filters = [ + dsl.Q( + "term", + **{"pids.doi.identifier.keyword": record["pids"]["doi"]["identifier"]}, + ), + ] + filter = dsl.Q("bool", filter=doi_filters) + + created_records = current_rdm_records_service.search( + system_identity, params={"allversions": True}, extra_filter=filter + ) + + assert created_records.total == 1 + + original_record = current_rdm_records_service.read(system_identity, record["id"]) + assert original_record._record.versions.latest_index == 2 + new_version = get_record_by_version(original_record.data["parent"]["id"], 2) + assert new_version.data["metadata"]["resource_type"]["id"] == "publication-article" + assert new_version.data["metadata"]["publication_date"] == "2018" + assert new_version.data["metadata"]["title"] == "Upgrade Software and Computing" + assert { + "identifier": "2707794", + "scheme": "inspire", + "relation_type": {"id": "isversionof"}, + "resource_type": {"id": "publication-other"}, + } in new_version.data["metadata"]["related_identifiers"] + + # clean up for other tests + running_app.app.config["RDM_PERSISTENT_IDENTIFIERS"]["doi"]["required"] = False + + +def test_update_record_with_CDS_DOI_multiple_doc_types( + running_app, location, scientific_community, minimal_record, datastream_config +): + pass def test_update_migrated_record_with_CDS_DOI( - running_app, location, scientific_community + running_app, location, scientific_community ): """Test update record with CDS DOI - should raise exception to handle manually.""" passed = False -def test_update_no_CDS_DOI(running_app, location, scientific_community): +def test_update_no_CDS_DOI_one_doc_type(running_app, location, scientific_community): """Test update migrated record without CDS DOI - no record matched.""" passed = False + + +def test_update_no_CDS_DOI_multiple_doc_types(running_app, location, + scientific_community, + datastream_config, minimal_record): + + minimal_record["metadata"]["resource_type"] = {"id": "publication-preprint"} + minimal_record["metadata"]["related_identifiers"] = [{ + "identifier": "2104.13342", + "scheme": "arxiv", + "relation_type": {"id": "isversionof"}, + "resource_type": {"id": "publication-other"}, + }] + + draft = current_rdm_records_service.create(system_identity, minimal_record) + record = current_rdm_records_service.publish(system_identity, draft.id) + with open( + "tests/inspire_harvester/data/record_no_cds_DOI_multiple_doc_type.json", + "r", + ) as f: + new_record = json.load(f) diff --git a/site/tests/inspire_harvester/test_writer.py b/site/tests/inspire_harvester/test_writer.py index 59fbce79..a4bf61d4 100644 --- a/site/tests/inspire_harvester/test_writer.py +++ b/site/tests/inspire_harvester/test_writer.py @@ -111,38 +111,6 @@ def transformed_record_2_files(): } -@pytest.fixture(scope="function") -def transformed_record_no_files(): - """Transformed via InspireJsonTransformer record with no files.""" - return { - "id": "1695540", - "metadata": { - "title": "Helium II heat transfer in LHC magnets", - "additional_titles": [ - {"title": "Polyimide cable insulation", "type": {"id": "subtitle"}} - ], - "publication_date": "2017", - "resource_type": {"id": "publication-dissertation"}, - "creators": [ - {"person_or_org": {"type": "personal", "family_name": "Hanks, Tom"}}, - {"person_or_org": {"type": "personal", "family_name": "Potter, Harry"}}, - {"person_or_org": {"type": "personal", "family_name": "Weasley, Ron"}}, - ], - "related_identifiers": [ - { - "identifier": "1695540", - "scheme": "inspire", - "relation_type": {"id": "isversionof"}, - "resource_type": {"id": "publication-other"}, - } - ], - }, - "files": {"enabled": False}, - "parent": {"access": {"owned_by": {"user": 2}}}, - "access": {"record": "public", "files": "public"}, - } - - def test_writer_1_rec_1_file( running_app, location, transformed_record_1_file, scientific_community ): diff --git a/site/tests/inspire_harvester/utils.py b/site/tests/inspire_harvester/utils.py index 9274c6a3..f620d315 100644 --- a/site/tests/inspire_harvester/utils.py +++ b/site/tests/inspire_harvester/utils.py @@ -6,7 +6,6 @@ # under the terms of the MIT License; see LICENSE file for more details. """Pytest utils module.""" - from unittest.mock import Mock, patch from celery import current_app @@ -39,6 +38,7 @@ def run_harvester_mock(datastream_cfg, mock_content_function): ): process_datastream(config=datastream_cfg["config"]) tasks = current_app.control.inspect() + while True: if not tasks.scheduled(): break From b380ba6ad92856764162c98e7421d5e1735753d4 Mon Sep 17 00:00:00 2001 From: Karolina Przerwa Date: Mon, 10 Nov 2025 09:41:38 +0100 Subject: [PATCH 2/9] feat(harvester): add hierarchy of resource types assignment --- .../inspire_harvester/transform_entry.py | 105 +- site/cds_rdm/inspire_harvester/writer.py | 99 +- site/tests/conftest.py | 24 + .../data/record_with_2_CDS_ids.json | 8 + .../data/record_with_erratum_DOI.json | 563 ++++ ...ord_with_no_cds_DOI_multiple_doc_type.json | 4 +- ...rd_with_no_cds_DOI_multiple_doc_type2.json | 2422 +++++++++++++++++ .../data/record_with_scoap3_art.json | 354 +++ .../inspire_harvester/test_transformer.py | 83 +- .../test_update_create_logic.py | 133 +- site/tests/inspire_harvester/utils.py | 1 + site/tests/utils.py | 18 + 12 files changed, 3671 insertions(+), 143 deletions(-) create mode 100644 site/tests/inspire_harvester/data/record_with_2_CDS_ids.json create mode 100644 site/tests/inspire_harvester/data/record_with_erratum_DOI.json create mode 100644 site/tests/inspire_harvester/data/record_with_no_cds_DOI_multiple_doc_type2.json create mode 100644 site/tests/inspire_harvester/data/record_with_scoap3_art.json create mode 100644 site/tests/utils.py diff --git a/site/cds_rdm/inspire_harvester/transform_entry.py b/site/cds_rdm/inspire_harvester/transform_entry.py index 53c452bc..a5b2c14c 100644 --- a/site/cds_rdm/inspire_harvester/transform_entry.py +++ b/site/cds_rdm/inspire_harvester/transform_entry.py @@ -32,6 +32,7 @@ "thesis": "publication-dissertation", "note": "publication-technicalnote", "conference paper": "publication-conferencepaper", + "activity report": "publication-report", } @@ -279,6 +280,60 @@ def _transform_publication_date(self): ) return None + def _check_if_published_art(self): + """Check if record is published article. + + follows https://github.com/inspirehep/inspire-schemas/blob/369a2f78189d9711cda8ac83e4e7d9344cc888da/inspire_schemas/readers/literature.py#L338 + """ + + def is_citeable(publication_info): + """Check fields to define if the article is citeable.""" + + def _item_has_pub_info(item): + return all(key in item for key in ("journal_title", "journal_volume")) + + def _item_has_page_or_artid(item): + return any(key in item for key in ("page_start", "artid")) + + has_pub_info = any(_item_has_pub_info(item) for item in publication_info) + has_page_or_artid = any( + _item_has_page_or_artid(item) for item in publication_info + ) + + return has_pub_info and has_page_or_artid + + pub_info = self.inspire_original_metadata.get("publication_info", []) + + citeable = pub_info and is_citeable(pub_info) + + submitted = "dois" in self.inspire_original_metadata and any( + "journal_title" in el for el in pub_info + ) + + return citeable or submitted + + def _select_document_type(self, doc_types): + """Select document types.""" + + priority = { + v: i + for i, v in enumerate( + [ + "conference paper", + "thesis", + "article", + "proceedings", + "report", + "activity report", + "note", + ] + ) + } + + # Select the candidate with the highest priority (lowest rank) + best_value = min(doc_types, key=lambda v: priority.get(v, float("inf"))) + return best_value + def _transform_document_type(self): """Mapping of INSPIRE document type to resource type.""" inspire_id = self.inspire_id @@ -294,15 +349,14 @@ def _transform_document_type(self): # Check for multiple document types - fail for now if len(document_types) > 1: - self.metadata_errors.append( - f"Multiple document types found: {document_types}. INSPIRE#: {inspire_id}. " - f"Multiple document types are not supported yet." + document_type = self._select_document_type(document_types) + self.logger.info( + f"Multiple document types found: {document_types}, mapped to {document_type}" ) - self.logger.error(f"Multiple document types found: {document_types}") - return None + else: + # Get the single document type + document_type = document_types[0] - # Get the single document type - document_type = document_types[0] self.logger.debug(f"Document type found: {document_type}") # Use the reusable mapping @@ -315,7 +369,12 @@ def _transform_document_type(self): self.logger.error(f"Unmapped document type: {document_type}") return None - mapped_resource_type = INSPIRE_DOCUMENT_TYPE_MAPPING[document_type] + if document_type == "article" and not self._check_if_published_art(): + # preprint type does not exist in inspire, it is computed + mapped_resource_type = "publication-preprint" + + else: + mapped_resource_type = INSPIRE_DOCUMENT_TYPE_MAPPING[document_type] self.logger.info( f"Mapped document type '{document_type}' to resource type '{mapped_resource_type}'" ) @@ -617,7 +676,7 @@ def _transform_related_identifiers(self): identifiers.append( { "scheme": "arxiv", - "identifier": arxiv_id, + "identifier": f"arXiv:{arxiv_id["value"]}", "relation_type": {"id": "isvariantformof"}, "resource_type": {"id": "publication-other"}, } @@ -1084,3 +1143,31 @@ def transform_files(self): f"Files transformation completed with {len(self.files_errors)} errors" ) return transformed_files, self.files_errors + + +# inspire enums https://github.com/inspirehep/inspire-schemas/blob/369a2f78189d9711cda8ac83e4e7d9344cc888da/inspire_schemas/records/elements + +# materials +# - addendum +# - additional material +# - data +# - editorial note +# - erratum +# - part +# - preprint +# - publication +# - reprint +# - software +# - translation +# - version + +# document types +# - activity report +# - article +# - book +# - book chapter +# - conference paper +# - note +# - proceedings +# - report +# - thesis diff --git a/site/cds_rdm/inspire_harvester/writer.py b/site/cds_rdm/inspire_harvester/writer.py index 2b7bb6e5..4a649095 100644 --- a/site/cds_rdm/inspire_harvester/writer.py +++ b/site/cds_rdm/inspire_harvester/writer.py @@ -9,6 +9,7 @@ import logging import time from collections import OrderedDict +from copy import deepcopy from io import BytesIO import requests @@ -69,7 +70,7 @@ def _write_entry(self, stream_entry, *args, inspire_id=None, logger=None, **kwar @hlog def _process_entry( - self, stream_entry, *args, inspire_id=None, logger=None, **kwargs + self, stream_entry, *args, inspire_id=None, logger=None, **kwargs ): """Helper method to process a single entry.""" error_message = None @@ -107,22 +108,18 @@ def write_many(self, stream_entries, *args, **kwargs): def _retrieve_identifier(self, identifiers, scheme): """Retrieve identifier by scheme.""" return next( - ( - d["identifier"] - for d in identifiers - if d["scheme"] == scheme - ), + (d["identifier"] for d in identifiers if d["scheme"] == scheme), None, ) @hlog def _get_existing_records( - self, stream_entry, inspire_id=None, logger=None, record_pid=None + self, stream_entry, inspire_id=None, logger=None, record_pid=None ): """Find records that have already been harvested from INSPIRE.""" entry = stream_entry.entry - doi = entry["pids"].get("doi", {}).get("identifier") + doi = entry.get("pids", {}).get("doi", {}).get("identifier") related_identifiers = entry["metadata"].get("related_identifiers", []) cds_id = self._retrieve_identifier(related_identifiers, "cds") @@ -187,7 +184,7 @@ def _get_existing_records( @hlog def update_record( - self, stream_entry, record_pid=None, inspire_id=None, logger=None + self, stream_entry, record_pid=None, inspire_id=None, logger=None ): """Update existing record.""" entry = stream_entry.entry @@ -211,15 +208,21 @@ def update_record( logger.debug(f"Existing files' checksums: {existing_checksums}.") logger.debug(f"New files' checksums: {new_checksums}.") - has_external_doi = ( - record.data["pids"].get("doi", {}).get("provider") == "external" + existing_record_has_doi = record.data["pids"].get("doi", {}) + existing_record_has_cds_doi = ( + record.data["pids"].get("doi", {}).get("provider") == "datacite" ) + + should_update_files = ( + existing_checksums != new_checksums + ) + should_create_new_version = ( - existing_checksums != new_checksums and not has_external_doi + existing_checksums != new_checksums and existing_record_has_doi and existing_record_has_cds_doi ) - should_update_files = existing_checksums != new_checksums and has_external_doi files_enabled = record_dict.get("files", {}).get("enabled", False) + if should_update_files and not files_enabled: stream_entry.entry["files"]["enabled"] = True @@ -235,7 +238,7 @@ def update_record( logger.debug(f"Draft created with ID: {draft.id}") - current_rdm_records_service.update_draft( + draft = current_rdm_records_service.update_draft( system_identity, draft.id, data=entry ) @@ -263,20 +266,19 @@ def update_record( ) except Exception as e: current_rdm_records_service.delete_draft(system_identity, draft["id"]) - raise e - # raise WriterError( - # f"Draft {draft.id} failed publishing because of an unexpected error: {str(e)}." - # ) + raise WriterError( + f"Draft {draft.id} failed publishing because of an unexpected error: {str(e)}." + ) @hlog def _update_files( - self, - stream_entry, - new_draft, - record, - record_pid=None, - inspire_id=None, - logger=None, + self, + stream_entry, + new_draft, + record, + record_pid=None, + inspire_id=None, + logger=None, ): entry = stream_entry.entry @@ -327,7 +329,7 @@ def _update_files( @hlog def _create_new_version( - self, stream_entry, record, inspire_id=None, record_pid=None, logger=None + self, stream_entry, record, inspire_id=None, record_pid=None, logger=None ): """For records with updated files coming from INSPIRE, create and publish a new version.""" entry = stream_entry.entry @@ -335,12 +337,15 @@ def _create_new_version( system_identity, record["id"] ) + new_version_entry = deepcopy(entry) # delete the previous DOI for new version - del entry["pids"] + if "pids" in entry: + del new_version_entry["pids"] + logger.debug(f"New version draft created with ID: {new_version_draft.id}") new_version_draft = current_rdm_records_service.update_draft( - system_identity, new_version_draft.id, entry + system_identity, new_version_draft.id, new_version_entry ) if record.data.get("files", {}).get("enabled", False): @@ -385,7 +390,7 @@ def _create_new_version( @hlog def _add_community( - self, stream_entry, draft, inspire_id=None, record_pid=None, logger=None + self, stream_entry, draft, inspire_id=None, record_pid=None, logger=None ): """Add CERN Scientific Community to the draft.""" with db.session.begin_nested(): @@ -401,14 +406,14 @@ def _add_community( @hlog def _create_new_record( - self, stream_entry, record_pid=None, inspire_id=None, logger=None + self, stream_entry, record_pid=None, inspire_id=None, logger=None ): """For new records coming from INSPIRE, create and publish a draft in CDS.""" entry = stream_entry.entry - doi = entry["pids"].get("doi", {}) + doi = entry.get("pids", {}).get("doi", {}) DATACITE_PREFIX = current_app.config["DATACITE_PREFIX"] - is_cds = DATACITE_PREFIX in doi["identifier"] + is_cds = DATACITE_PREFIX in doi.get("identifier", "") if is_cds: raise WriterError("Trying to create record with CDS DOI") @@ -477,13 +482,13 @@ def _create_new_record( @hlog def _fetch_file( - self, - stream_entry, - inspire_url, - max_retries=3, - inspire_id=None, - record_pid=None, - logger=None, + self, + stream_entry, + inspire_url, + max_retries=3, + inspire_id=None, + record_pid=None, + logger=None, ): """Fetch file content from inspire url.""" logger.debug(f"File URL: {inspire_url}") @@ -525,14 +530,14 @@ def _fetch_file( @hlog def _create_file( - self, - stream_entry, - file_data, - file_content, - draft, - inspire_id=None, - record_pid=None, - logger=None, + self, + stream_entry, + file_data, + file_content, + draft, + inspire_id=None, + record_pid=None, + logger=None, ): """Create a new file.""" logger.debug(f"Filename: '{file_data['key']}'.") diff --git a/site/tests/conftest.py b/site/tests/conftest.py index 08cee277..8d90e60a 100644 --- a/site/tests/conftest.py +++ b/site/tests/conftest.py @@ -60,6 +60,7 @@ from cds_rdm.permissions import ( CDSCommunitiesPermissionPolicy, CDSRDMRecordPermissionPolicy, + lock_edit_record_published_files, ) from cds_rdm.schemes import is_cds, is_inspire, is_inspire_author @@ -260,6 +261,7 @@ def app_config(app_config, mock_datacite_client): label=_("Concept DOI"), ), ] + app_config["RDM_LOCK_EDIT_PUBLISHED_FILES"] = lock_edit_record_published_files return app_config @@ -726,6 +728,28 @@ def resource_type_v(app, resource_type_type): }, ) + vocabulary_service.create( + system_identity, + { + "id": "publication-preprint", # Previously publication-thesis + "icon": "file alternate", + "props": { + "csl": "prepriny", + "datacite_general": "Preprint", + "datacite_type": "", + "openaire_resourceType": "0044", + "openaire_type": "publication", + "eurepo": "info:eu-repo/semantics/other", + "schema.org": "https://schema.org/Preprint", + "subtype": "publication-preprint", + "type": "publication", + }, + "title": {"en": "Preprint", "de": "Preprint"}, + "tags": ["depositable", "linkable"], + "type": "resourcetypes", + }, + ) + vocabulary_service.create( system_identity, { diff --git a/site/tests/inspire_harvester/data/record_with_2_CDS_ids.json b/site/tests/inspire_harvester/data/record_with_2_CDS_ids.json new file mode 100644 index 00000000..2891ef45 --- /dev/null +++ b/site/tests/inspire_harvester/data/record_with_2_CDS_ids.json @@ -0,0 +1,8 @@ +{ + "hits": { + "total": 1, + "hits": [ + {"metadata":{"citation_count_without_self_citations":28,"documents":[{"key":"26aec07fc9c6bea56cde13f86d74d96b","url":"https://inspirehep.net/files/26aec07fc9c6bea56cde13f86d74d96b","filename":"PhysRevD.104.L111102.pdf","fulltext":true,"material":"publication"}],"publication_info":[{"year":2021,"artid":"L111102","material":"publication","journal_issue":"11","journal_title":"Phys.Rev.D","journal_record":{"$ref":"https://inspirehep.net/api/journals/1613970"},"journal_volume":"104"}],"citation_count":36,"core":true,"dois":[{"value":"10.1103/PhysRevD.104.L111102","source":"APS","material":"publication"},{"value":"10.1103/PhysRevD.104.L111102","source":"arXiv","material":"publication"}],"titles":[{"title":"Search for pseudoscalar bosons decaying into $e^+e^-$ pairs in the NA64 experiment at the CERN SPS","source":"APS"},{"title":"Search for pseudoscalar bosons decaying into $e^+e^-$ pairs in the NA64 experiment at the CERN SPS","source":"arXiv"},{"title":"Search for pseudoscalar bosons decaying into $e^+e^-$ pairs","source":"arXiv"}],"$schema":"https://inspirehep.net/schemas/records/hep.json","authors":[{"uuid":"30c36b14-c7d9-44b9-a7cc-d005c741043d","record":{"$ref":"https://inspirehep.net/api/authors/1992536"},"full_name":"Andreev, Yu.M.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Moscow, INR","record":{"$ref":"https://inspirehep.net/api/institutions/903030"}}],"signature_block":"ANDRAFy","raw_affiliations":[{"value":"Institute for Nuclear Research, 117312 Moscow, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Institute for Nuclear Research,117312 Moscow,Russia"}]},{"uuid":"56f80aad-7c93-464c-ab7a-e6c74d45c4bf","record":{"$ref":"https://inspirehep.net/api/authors/1034884"},"full_name":"Banerjee, D.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"CERN","record":{"$ref":"https://inspirehep.net/api/institutions/902725"}}],"signature_block":"BANARJYd","raw_affiliations":[{"value":"CERN, European Organization for Nuclear Research, CH-1211 Geneva, Switzerland","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"CERN,European Organization for Nuclear Research,CH-1211 Geneva,Switzerland"}]},{"uuid":"e93c74fa-e51c-42ea-a064-b4ee350fc991","record":{"$ref":"https://inspirehep.net/api/authors/1070564"},"full_name":"Bernhard, J.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"CERN","record":{"$ref":"https://inspirehep.net/api/institutions/902725"}}],"signature_block":"BARNADj","curated_relation":true,"raw_affiliations":[{"value":"CERN, European Organization for Nuclear Research, CH-1211 Geneva, Switzerland","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"CERN,European Organization for Nuclear Research,CH-1211 Geneva,Switzerland"}]},{"uuid":"331a0b13-0d52-4ee8-893d-2787357c730b","record":{"$ref":"https://inspirehep.net/api/authors/1876562"},"full_name":"Burtsev, V.E.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Dubna, JINR","record":{"$ref":"https://inspirehep.net/api/institutions/902780"}}],"signature_block":"BARTSAFv","raw_affiliations":[{"value":"Joint Institute for Nuclear Research, 141980 Dubna, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Joint Institute for Nuclear Research,141980 Dubna,Russia"}]},{"uuid":"99433175-63d7-471d-86f2-7f789db294a6","record":{"$ref":"https://inspirehep.net/api/authors/1599780"},"full_name":"Charitonidis, N.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"CERN","record":{"$ref":"https://inspirehep.net/api/institutions/902725"}}],"signature_block":"CARATANADn","raw_affiliations":[{"value":"CERN, European Organization for Nuclear Research, CH-1211 Geneva, Switzerland","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"CERN,European Organization for Nuclear Research,CH-1211 Geneva,Switzerland"}]},{"uuid":"1cadff79-e1e2-4f88-8bcc-0443a335481a","record":{"$ref":"https://inspirehep.net/api/authors/1876563"},"full_name":"Chumakov, A.G.","affiliations":[{"value":"Tomsk Pedagogical Inst.","record":{"$ref":"https://inspirehep.net/api/institutions/903659"}},{"value":"Tomsk Polytechnic U.","record":{"$ref":"https://inspirehep.net/api/institutions/903660"}},{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}}],"signature_block":"CANACAVa","raw_affiliations":[{"value":"Tomsk State Pedagogical University, 634061 Tomsk, Russia","source":"APS"},{"value":"Tomsk Polytechnic University, 634050 Tomsk, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Tomsk Polytechnic University,634050 Tomsk,Russia"},{"value":"Tomsk State Pedagogical University,634061 Tomsk,Russia"}]},{"uuid":"22c878ba-f0ec-4b56-8071-f18143c8fa14","record":{"$ref":"https://inspirehep.net/api/authors/1910368"},"full_name":"Cooke, D.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"University Coll. London","record":{"$ref":"https://inspirehep.net/api/institutions/903311"}}],"signature_block":"CACd","raw_affiliations":[{"value":"UCL Departement of Physics and Astronomy, University College London, Gower St., London WC1E 6BT, United Kingdom","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"UCL Departement of Physics and Astronomy,University College London,Gower St. London WC1E 6BT,United Kingdom"}]},{"uuid":"203effd7-1599-4101-997d-1e055be92e48","record":{"$ref":"https://inspirehep.net/api/authors/1045321"},"full_name":"Crivelli, P.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Zurich, ETH","record":{"$ref":"https://inspirehep.net/api/institutions/903369"}}],"signature_block":"CRAVALp","raw_affiliations":[{"value":"ETH Zürich, Institute for Particle Physics and Astrophysics, CH-8093 Zürich, Switzerland","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"ETH Zürich,Institute for Particle Physics and Astrophysics,CH-8093 Zürich,Switzerland"}]},{"uuid":"6341f628-81c3-44d3-8479-c7b06b0f35d4","record":{"$ref":"https://inspirehep.net/api/authors/1876560"},"full_name":"Depero, E.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Zurich, ETH","record":{"$ref":"https://inspirehep.net/api/institutions/903369"}}],"signature_block":"DAPARe","raw_affiliations":[{"value":"ETH Zürich, Institute for Particle Physics and Astrophysics, CH-8093 Zürich, Switzerland","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"ETH Zürich,Institute for Particle Physics and Astrophysics,CH-8093 Zürich,Switzerland"}]},{"uuid":"e3cc2400-412b-49da-8c0e-1679658413fd","record":{"$ref":"https://inspirehep.net/api/authors/1876565"},"full_name":"Dermenev, A.V.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Moscow, INR","record":{"$ref":"https://inspirehep.net/api/institutions/903030"}}],"signature_block":"DARNANAFa","raw_affiliations":[{"value":"Institute for Nuclear Research, 117312 Moscow, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Institute for Nuclear Research,117312 Moscow,Russia"}]},{"uuid":"0fb4f1a6-a102-4e0b-83bd-8ba80e031135","record":{"$ref":"https://inspirehep.net/api/authors/1011541"},"full_name":"Donskov, S.V.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Serpukhov, IHEP","record":{"$ref":"https://inspirehep.net/api/institutions/903194"}}],"signature_block":"DANSCAVs","raw_affiliations":[{"value":"State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center ’Kurchatov Institute’ (IHEP), 142281 Protvino, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center 'Kurchatov Institute' (IHEP),142281 Protvino,Russia"}]},{"uuid":"0b1273b5-f1cb-4eb1-b3fa-165551d07362","record":{"$ref":"https://inspirehep.net/api/authors/1391711"},"full_name":"Dusaev, R.R.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Tomsk Polytechnic U.","record":{"$ref":"https://inspirehep.net/api/institutions/903660"}}],"signature_block":"DASAFr","raw_affiliations":[{"value":"Tomsk Polytechnic University, 634050 Tomsk, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Tomsk Polytechnic University,634050 Tomsk,Russia"}]},{"uuid":"9e4ed6b8-00b8-49fd-936c-0a5a5ad011ae","record":{"$ref":"https://inspirehep.net/api/authors/1607170"},"full_name":"Enik, T.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Dubna, JINR","record":{"$ref":"https://inspirehep.net/api/institutions/902780"}}],"signature_block":"ENACt","curated_relation":true,"raw_affiliations":[{"value":"Joint Institute for Nuclear Research, 141980 Dubna, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Joint Institute for Nuclear Research,141980 Dubna,Russia"}]},{"uuid":"f74dec1d-e8d8-423c-852a-ede4ded435d4","record":{"$ref":"https://inspirehep.net/api/authors/1992538"},"full_name":"Feshchenko, A.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Dubna, JINR","record":{"$ref":"https://inspirehep.net/api/institutions/902780"}}],"signature_block":"FASCANCa","raw_affiliations":[{"value":"Joint Institute for Nuclear Research, 141980 Dubna, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Joint Institute for Nuclear Research,141980 Dubna,Russia"}]},{"uuid":"37bfa584-6c55-47d6-a3ec-93213f299322","record":{"$ref":"https://inspirehep.net/api/authors/1009360"},"full_name":"Frolov, V.N.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Dubna, JINR","record":{"$ref":"https://inspirehep.net/api/institutions/902780"}}],"signature_block":"FRALAVv","raw_affiliations":[{"value":"Joint Institute for Nuclear Research, 141980 Dubna, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Joint Institute for Nuclear Research,141980 Dubna,Russia"}]},{"uuid":"7a8065a4-8b2d-46d0-9099-98eccb27e6d5","record":{"$ref":"https://inspirehep.net/api/authors/1283538"},"full_name":"Gardikiotis, A.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Patras U.","record":{"$ref":"https://inspirehep.net/api/institutions/903743"}}],"signature_block":"GARDACATa","raw_affiliations":[{"value":"Physics Department, University of Patras, 265 04 Patras, Greece","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Physics Department,University of Patras,265 04 Patras,Greece"}]},{"uuid":"4554f0da-1233-4758-97af-c3204ec6210a","record":{"$ref":"https://inspirehep.net/api/authors/1008577"},"full_name":"Gerassimov, S.G.","affiliations":[{"value":"TUM-IAS, Munich","record":{"$ref":"https://inspirehep.net/api/institutions/911544"}},{"value":"Lebedev Inst.","record":{"$ref":"https://inspirehep.net/api/institutions/902955"}},{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Munich, Tech. U.","record":{"$ref":"https://inspirehep.net/api/institutions/903037"}},{"value":"LPI, Moscow (main)","record":{"$ref":"https://inspirehep.net/api/institutions/1256696"}}],"signature_block":"GARASANAVs","raw_affiliations":[{"value":"Technische Universität München, Physik Department, 85748 Garching, Germany","source":"APS"},{"value":"P.N. Lebedev Physical Institute of the Russian Academy of Sciences, 119 991 Moscow, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Technische Universität München,Physik Department,85748 Garching,Germany"},{"value":"P.N. Lebedev Physical Institute,Moscow,Russia,119 991 Moscow,Russia"}]},{"uuid":"00e5f85a-8aa0-45ef-9487-8bf617e55089","record":{"$ref":"https://inspirehep.net/api/authors/1008194"},"full_name":"Gninenko, S.N.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Moscow, INR","record":{"$ref":"https://inspirehep.net/api/institutions/903030"}}],"signature_block":"GNANANCs","raw_affiliations":[{"value":"Institute for Nuclear Research, 117312 Moscow, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Institute for Nuclear Research,117312 Moscow,Russia"}]},{"uuid":"1e297ef8-0117-4f7d-83a2-d27add76e4e6","record":{"$ref":"https://inspirehep.net/api/authors/1876567"},"full_name":"Hösgen, M.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Bonn U., HISKP","record":{"$ref":"https://inspirehep.net/api/institutions/908572"}}],"signature_block":"HASGANm","raw_affiliations":[{"value":"Universität Bonn, Helmholtz-Institut für Strahlen-und Kernphysik, 53115 Bonn, Germany","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Universität Bonn,Helmholtz-Institut für Strahlen-und Kernphysik,53115 Bonn,Germany"}]},{"uuid":"e9b6b061-30d1-4fd9-b5c0-4981f7482b20","record":{"$ref":"https://inspirehep.net/api/authors/1639537"},"full_name":"Jeckel, M.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"CERN","record":{"$ref":"https://inspirehep.net/api/institutions/902725"}}],"signature_block":"JACALm","raw_affiliations":[{"value":"CERN, European Organization for Nuclear Research, CH-1211 Geneva, Switzerland","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"CERN,European Organization for Nuclear Research,CH-1211 Geneva,Switzerland"}]},{"uuid":"177ed12a-e59b-4f43-b456-31b20d34f7db","record":{"$ref":"https://inspirehep.net/api/authors/1050796"},"full_name":"Kachanov, V.A.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Serpukhov, IHEP","record":{"$ref":"https://inspirehep.net/api/institutions/903194"}}],"signature_block":"CACANAVv","raw_affiliations":[{"value":"State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center ’Kurchatov Institute’ (IHEP), 142281 Protvino, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center 'Kurchatov Institute' (IHEP),142281 Protvino,Russia"}]},{"uuid":"1a007fb2-f57c-45ed-b618-41323d58c0e8","record":{"$ref":"https://inspirehep.net/api/authors/1876568"},"full_name":"Karneyeu, A.E.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Moscow, INR","record":{"$ref":"https://inspirehep.net/api/institutions/903030"}}],"signature_block":"CARNYa","raw_affiliations":[{"value":"Institute for Nuclear Research, 117312 Moscow, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Institute for Nuclear Research,117312 Moscow,Russia"}]},{"uuid":"73f27656-2265-4b0a-9686-a064f4d8a6e1","record":{"$ref":"https://inspirehep.net/api/authors/1062433"},"full_name":"Kekelidze, G.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Dubna, JINR","record":{"$ref":"https://inspirehep.net/api/institutions/902780"}}],"signature_block":"CACALADSg","raw_affiliations":[{"value":"Joint Institute for Nuclear Research, 141980 Dubna, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Joint Institute for Nuclear Research,141980 Dubna,Russia"}]},{"uuid":"47bfbbcb-61a4-4eec-9e5e-812db0057706","record":{"$ref":"https://inspirehep.net/api/authors/1003144"},"full_name":"Ketzer, B.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Bonn U., HISKP","record":{"$ref":"https://inspirehep.net/api/institutions/908572"}}],"signature_block":"CATSARb","raw_affiliations":[{"value":"Universität Bonn, Helmholtz-Institut für Strahlen-und Kernphysik, 53115 Bonn, Germany","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Universität Bonn,Helmholtz-Institut für Strahlen-und Kernphysik,53115 Bonn,Germany"}]},{"uuid":"8621869e-bfa4-4ae8-9786-ffa8e52c0ba0","record":{"$ref":"https://inspirehep.net/api/authors/1074204"},"full_name":"Kirpichnikov, D.V.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Moscow, INR","record":{"$ref":"https://inspirehep.net/api/institutions/903030"}}],"signature_block":"CARPACHNACAVd","raw_affiliations":[{"value":"Institute for Nuclear Research, 117312 Moscow, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Institute for Nuclear Research,117312 Moscow,Russia"}]},{"uuid":"e36b4781-4ed0-4c0c-afdb-c33b8ac547cf","record":{"$ref":"https://inspirehep.net/api/authors/1002797"},"full_name":"Kirsanov, M.M.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Moscow, INR","record":{"$ref":"https://inspirehep.net/api/institutions/903030"}}],"signature_block":"CARSANAVm","raw_affiliations":[{"value":"Institute for Nuclear Research, 117312 Moscow, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Institute for Nuclear Research,117312 Moscow,Russia"}]},{"uuid":"427de10c-cbbd-444a-95cc-cc8378bcdd66","record":{"$ref":"https://inspirehep.net/api/authors/1907803"},"full_name":"Kolosov, V.N.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Serpukhov, IHEP","record":{"$ref":"https://inspirehep.net/api/institutions/903194"}}],"signature_block":"CALASAVv","raw_affiliations":[{"value":"State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center ’Kurchatov Institute’ (IHEP), 142281 Protvino, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center 'Kurchatov Institute' (IHEP),142281 Protvino,Russia"}]},{"uuid":"8b0e9272-9831-4ed0-b94e-f74ba620817c","record":{"$ref":"https://inspirehep.net/api/authors/1002227"},"full_name":"Konorov, I.V.","affiliations":[{"value":"TUM-IAS, Munich","record":{"$ref":"https://inspirehep.net/api/institutions/911544"}},{"value":"Lebedev Inst.","record":{"$ref":"https://inspirehep.net/api/institutions/902955"}},{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Munich, Tech. U.","record":{"$ref":"https://inspirehep.net/api/institutions/903037"}},{"value":"LPI, Moscow (main)","record":{"$ref":"https://inspirehep.net/api/institutions/1256696"}}],"signature_block":"CANARAVi","raw_affiliations":[{"value":"Technische Universität München, Physik Department, 85748 Garching, Germany","source":"APS"},{"value":"P.N. Lebedev Physical Institute of the Russian Academy of Sciences, 119 991 Moscow, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Technische Universität München,Physik Department,85748 Garching,Germany"},{"value":"P.N. Lebedev Physical Institute,Moscow,Russia,119 991 Moscow,Russia"}]},{"uuid":"2cffe3e3-8b61-41f1-89b5-b3faa7002053","record":{"$ref":"https://inspirehep.net/api/authors/1001998"},"full_name":"Kovalenko, S.G.","affiliations":[{"value":"Chile U., Santiago","record":{"$ref":"https://inspirehep.net/api/institutions/902731"}},{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Andres Bello Natl. U.","record":{"$ref":"https://inspirehep.net/api/institutions/907969"}}],"signature_block":"CAVALANCs","raw_affiliations":[{"value":"Millennium Institute for Subatomic Physics at the High-Energy Frontier (SAPHIR) of ANID, Fernández Concha 700, Santiago, Chile","source":"APS"},{"value":"Departamento de Ciencias Físicas, Universidad Andres Bello, Sazié 2212, Piso 7, Santiago, Chile","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Departamento de Ciencias Físicas,Universidad Andres Bello,Sazié2212,Piso 7,Santiago,Chile"}]},{"uuid":"ff26c423-6958-4612-a3a9-a0c38cc09d79","record":{"$ref":"https://inspirehep.net/api/authors/1001923"},"full_name":"Kramarenko, V.A.","affiliations":[{"value":"SINP, Moscow","record":{"$ref":"https://inspirehep.net/api/institutions/908795"}},{"value":"Dubna, JINR","record":{"$ref":"https://inspirehep.net/api/institutions/902780"}},{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}}],"signature_block":"CRANARANCv","raw_affiliations":[{"value":"Skobeltsyn Institute of Nuclear Physics, Lomonosov Moscow State University, 119991 Moscow, Russia","source":"APS"},{"value":"Joint Institute for Nuclear Research, 141980 Dubna, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Joint Institute for Nuclear Research,141980 Dubna,Russia"},{"value":"Skobeltsyn Institute of Nuclear Physics,Lomonosov Moscow State University,119991 Moscow,Russia"}]},{"uuid":"f84024e3-1b0f-4fd7-a703-e8f3a58e3aaa","record":{"$ref":"https://inspirehep.net/api/authors/1070742"},"full_name":"Kravchuk, L.V.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Moscow, INR","record":{"$ref":"https://inspirehep.net/api/institutions/903030"}}],"signature_block":"CRAVCACl","raw_affiliations":[{"value":"Institute for Nuclear Research, 117312 Moscow, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Institute for Nuclear Research,117312 Moscow,Russia"}]},{"uuid":"06850883-3395-4644-b2b6-bdc2927afd34","record":{"$ref":"https://inspirehep.net/api/authors/1001900"},"full_name":"Krasnikov, N.V.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Dubna, JINR","record":{"$ref":"https://inspirehep.net/api/institutions/902780"}},{"value":"Moscow, INR","record":{"$ref":"https://inspirehep.net/api/institutions/903030"}}],"signature_block":"CRASNACAVn","raw_affiliations":[{"value":"Joint Institute for Nuclear Research, 141980 Dubna, Russia","source":"APS"},{"value":"Institute for Nuclear Research, 117312 Moscow, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Joint Institute for Nuclear Research,141980 Dubna,Russia"},{"value":"Institute for Nuclear Research,117312 Moscow,Russia"}]},{"uuid":"19eb1116-242c-498a-bdb5-704535e1f396","record":{"$ref":"https://inspirehep.net/api/authors/1001595"},"full_name":"Kuleshov, S.V.","affiliations":[{"value":"Chile U., Santiago","record":{"$ref":"https://inspirehep.net/api/institutions/902731"}},{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Andres Bello Natl. U.","record":{"$ref":"https://inspirehep.net/api/institutions/907969"}}],"signature_block":"CALASAVs","raw_affiliations":[{"value":"Millennium Institute for Subatomic Physics at the High-Energy Frontier (SAPHIR) of ANID, Fernández Concha 700, Santiago, Chile","source":"APS"},{"value":"Departamento de Ciencias Físicas, Universidad Andres Bello, Sazié 2212, Piso 7, Santiago, Chile","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Departamento de Ciencias Físicas,Universidad Andres Bello,Sazié2212,Piso 7,Santiago,Chile"}]},{"uuid":"ae331996-d6fc-49d7-a727-8705a87632af","record":{"$ref":"https://inspirehep.net/api/authors/999453"},"full_name":"Lyubovitskij, V.E.","affiliations":[{"value":"Santa Maria U., Valparaiso","record":{"$ref":"https://inspirehep.net/api/institutions/904589"}},{"value":"Chile U., Santiago","record":{"$ref":"https://inspirehep.net/api/institutions/902731"}},{"value":"Tomsk Pedagogical Inst.","record":{"$ref":"https://inspirehep.net/api/institutions/903659"}},{"value":"Tomsk Polytechnic U.","record":{"$ref":"https://inspirehep.net/api/institutions/903660"}},{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"CCTVal, Valparaiso","record":{"$ref":"https://inspirehep.net/api/institutions/911929"}}],"signature_block":"LABAVATSCAJv","curated_relation":true,"raw_affiliations":[{"value":"Universidad Técnica Federico Santa María, Centro Científica y Tecnológica de Valparaíso-CCTVal, 2390123 Valparaíso, Chile","source":"APS"},{"value":"Millennium Institute for Subatomic Physics at the High-Energy Frontier (SAPHIR) of ANID, Fernández Concha 700, Santiago, Chile","source":"APS"},{"value":"Tomsk State Pedagogical University, 634061 Tomsk, Russia","source":"APS"},{"value":"Tomsk Polytechnic University, 634050 Tomsk, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Tomsk Polytechnic University,634050 Tomsk,Russia"},{"value":"Tomsk State Pedagogical University,634061 Tomsk,Russia"},{"value":"Universidad Técnica Federico Santa María,2390123 Valparaíso,Chile"}]},{"uuid":"cf10fe18-f156-4f61-a91e-d2b02868b44e","record":{"$ref":"https://inspirehep.net/api/authors/1992539"},"full_name":"Lysan, V.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Dubna, JINR","record":{"$ref":"https://inspirehep.net/api/institutions/902780"}}],"signature_block":"LASANv","raw_affiliations":[{"value":"Joint Institute for Nuclear Research, 141980 Dubna, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Joint Institute for Nuclear Research,141980 Dubna,Russia"}]},{"uuid":"2532affc-4ab4-4c61-a87d-ffe3ff1ff260","record":{"$ref":"https://inspirehep.net/api/authors/998249"},"full_name":"Matveev, V.A.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Dubna, JINR","record":{"$ref":"https://inspirehep.net/api/institutions/902780"}}],"signature_block":"MATVAFv","raw_affiliations":[{"value":"Joint Institute for Nuclear Research, 141980 Dubna, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Joint Institute for Nuclear Research,141980 Dubna,Russia"}]},{"uuid":"66107d8e-1a77-48a3-81cb-65abc12a2419","record":{"$ref":"https://inspirehep.net/api/authors/1043529"},"full_name":"Mikhailov, Yu.V.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Serpukhov, IHEP","record":{"$ref":"https://inspirehep.net/api/institutions/903194"}}],"signature_block":"MACALAVy","raw_affiliations":[{"value":"State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center ’Kurchatov Institute’ (IHEP), 142281 Protvino, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center 'Kurchatov Institute' (IHEP),142281 Protvino,Russia"}]},{"uuid":"b5e018b8-a003-4c25-b002-721b9064ec1e","record":{"$ref":"https://inspirehep.net/api/authors/1121788"},"full_name":"Molina Bueno, L.","affiliations":[{"value":"Zurich, ETH","record":{"$ref":"https://inspirehep.net/api/institutions/903369"}},{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}}],"signature_block":"BANl","raw_affiliations":[{"value":"ETH Zürich, Institute for Particle Physics and Astrophysics, CH-8093 Zürich, Switzerland","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV), Carrer del Catedrtic Jos Beltrn Martinez, 2, 46980 Paterna, Valencia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"ETH Zürich,Institute for Particle Physics and Astrophysics,CH-8093 Zürich,Switzerland"}]},{"uuid":"5c4fac53-ebc8-4e7f-990d-08faac1640ca","record":{"$ref":"https://inspirehep.net/api/authors/1992540"},"full_name":"Peshekhonov, D.V.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Dubna, JINR","record":{"$ref":"https://inspirehep.net/api/institutions/902780"}}],"signature_block":"PASACANAVd","raw_affiliations":[{"value":"Joint Institute for Nuclear Research, 141980 Dubna, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Joint Institute for Nuclear Research,141980 Dubna,Russia"}]},{"uuid":"a24812ae-eb43-48b0-90c1-91d07dbc4977","record":{"$ref":"https://inspirehep.net/api/authors/1898990"},"full_name":"Polyakov, V.A.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Serpukhov, IHEP","record":{"$ref":"https://inspirehep.net/api/institutions/903194"}}],"signature_block":"PALACAVv","raw_affiliations":[{"value":"State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center ’Kurchatov Institute’ (IHEP), 142281 Protvino, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center 'Kurchatov Institute' (IHEP),142281 Protvino,Russia"}]},{"uuid":"85040e98-9eb0-4eb7-adf2-4f9224a05690","record":{"$ref":"https://inspirehep.net/api/authors/1026910"},"full_name":"Radics, B.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Zurich, ETH","record":{"$ref":"https://inspirehep.net/api/institutions/903369"}}],"signature_block":"RADACb","raw_affiliations":[{"value":"ETH Zürich, Institute for Particle Physics and Astrophysics, CH-8093 Zürich, Switzerland","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"ETH Zürich,Institute for Particle Physics and Astrophysics,CH-8093 Zürich,Switzerland"}]},{"uuid":"bf9cb194-0a3d-4686-a0bb-94442c9ddf08","record":{"$ref":"https://inspirehep.net/api/authors/2128027"},"full_name":"Rojas, R.","affiliations":[{"value":"Santa Maria U., Valparaiso","record":{"$ref":"https://inspirehep.net/api/institutions/904589"}},{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"CCTVal, Valparaiso","record":{"$ref":"https://inspirehep.net/api/institutions/911929"}}],"signature_block":"RAJr","raw_affiliations":[{"value":"Universidad Técnica Federico Santa María, Centro Científica y Tecnológica de Valparaíso-CCTVal, 2390123 Valparaíso, Chile","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Universidad Técnica Federico Santa María,2390123 Valparaíso,Chile"}]},{"uuid":"6b2426a2-32af-4f8c-ad88-fd5601845e5b","record":{"$ref":"https://inspirehep.net/api/authors/991041"},"full_name":"Rubbia, A.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Zurich, ETH","record":{"$ref":"https://inspirehep.net/api/institutions/903369"}}],"signature_block":"RABa","raw_affiliations":[{"value":"ETH Zürich, Institute for Particle Physics and Astrophysics, CH-8093 Zürich, Switzerland","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"ETH Zürich,Institute for Particle Physics and Astrophysics,CH-8093 Zürich,Switzerland"}]},{"uuid":"bb8980b3-1240-486d-885d-ee6a7d390874","record":{"$ref":"https://inspirehep.net/api/authors/990489"},"full_name":"Samoylenko, V.D.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Serpukhov, IHEP","record":{"$ref":"https://inspirehep.net/api/institutions/903194"}}],"signature_block":"SANYLANCv","raw_affiliations":[{"value":"State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center ’Kurchatov Institute’ (IHEP), 142281 Protvino, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center 'Kurchatov Institute' (IHEP),142281 Protvino,Russia"}]},{"uuid":"be9589ea-a862-4567-b9fd-08a8cbcce30e","record":{"$ref":"https://inspirehep.net/api/authors/1876572"},"full_name":"Sieber, H.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Zurich, ETH","record":{"$ref":"https://inspirehep.net/api/institutions/903369"}}],"signature_block":"SABARh","raw_affiliations":[{"value":"ETH Zürich, Institute for Particle Physics and Astrophysics, CH-8093 Zürich, Switzerland","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"ETH Zürich,Institute for Particle Physics and Astrophysics,CH-8093 Zürich,Switzerland"}]},{"uuid":"bee6965d-9798-4c14-a470-21d1a2957fcb","record":{"$ref":"https://inspirehep.net/api/authors/1074204"},"full_name":"Shchukin, D.","affiliations":[{"value":"Lebedev Inst.","record":{"$ref":"https://inspirehep.net/api/institutions/902955"}},{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"LPI, Moscow (main)","record":{"$ref":"https://inspirehep.net/api/institutions/1256696"}}],"signature_block":"SCACANd","raw_affiliations":[{"value":"P.N. Lebedev Physical Institute of the Russian Academy of Sciences, 119 991 Moscow, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"P.N. Lebedev Physical Institute,Moscow,Russia,119 991 Moscow,Russia"}]},{"uuid":"a5721dad-1661-4b21-8e37-f86ff884ba07","record":{"$ref":"https://inspirehep.net/api/authors/1062381"},"full_name":"Tikhomirov, V.O.","affiliations":[{"value":"Lebedev Inst.","record":{"$ref":"https://inspirehep.net/api/institutions/902955"}},{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"LPI, Moscow (main)","record":{"$ref":"https://inspirehep.net/api/institutions/1256696"}}],"signature_block":"TACANARAVv","curated_relation":true,"raw_affiliations":[{"value":"P.N. Lebedev Physical Institute of the Russian Academy of Sciences, 119 991 Moscow, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"P.N. Lebedev Physical Institute,Moscow,Russia,119 991 Moscow,Russia"}]},{"uuid":"ef1ef9e0-9627-4660-9764-b5e6a582ed37","record":{"$ref":"https://inspirehep.net/api/authors/1876573"},"full_name":"Tlisova, I.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Moscow, INR","record":{"$ref":"https://inspirehep.net/api/institutions/903030"}}],"signature_block":"TLASAVi","raw_affiliations":[{"value":"Institute for Nuclear Research, 117312 Moscow, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Institute for Nuclear Research,117312 Moscow,Russia"}]},{"uuid":"ab8b1910-b100-4cca-a0f9-d892f29dad6d","record":{"$ref":"https://inspirehep.net/api/authors/1039258"},"full_name":"Toropin, A.N.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Moscow, INR","record":{"$ref":"https://inspirehep.net/api/institutions/903030"}}],"signature_block":"TARAPANa","raw_affiliations":[{"value":"Institute for Nuclear Research, 117312 Moscow, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Institute for Nuclear Research,117312 Moscow,Russia"}]},{"uuid":"d795e71b-24cc-4002-9f3c-ebc4abe1be03","record":{"$ref":"https://inspirehep.net/api/authors/1992552"},"full_name":"Trifonov, A.Yu.","affiliations":[{"value":"Tomsk Pedagogical Inst.","record":{"$ref":"https://inspirehep.net/api/institutions/903659"}},{"value":"Tomsk Polytechnic U.","record":{"$ref":"https://inspirehep.net/api/institutions/903660"}},{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}}],"signature_block":"TRAFANAVa","raw_affiliations":[{"value":"Tomsk State Pedagogical University, 634061 Tomsk, Russia","source":"APS"},{"value":"Tomsk Polytechnic University, 634050 Tomsk, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Tomsk Polytechnic University,634050 Tomsk,Russia"},{"value":"Tomsk State Pedagogical University,634061 Tomsk,Russia"}]},{"uuid":"5b193a05-0a8c-4b43-b35e-1032f5280763","record":{"$ref":"https://inspirehep.net/api/authors/1876575"},"full_name":"Vasilishin, B.I.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Tomsk Polytechnic U.","record":{"$ref":"https://inspirehep.net/api/institutions/903660"}}],"signature_block":"VASALASANb","raw_affiliations":[{"value":"Tomsk Polytechnic University, 634050 Tomsk, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Tomsk Polytechnic University,634050 Tomsk,Russia"}]},{"uuid":"6ef1a9c5-63bb-4d69-9392-2cc856b603ff","record":{"$ref":"https://inspirehep.net/api/authors/1910374"},"full_name":"Vasquez Arenas, G.","affiliations":[{"value":"Santa Maria U., Valparaiso","record":{"$ref":"https://inspirehep.net/api/institutions/904589"}},{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"CCTVal, Valparaiso","record":{"$ref":"https://inspirehep.net/api/institutions/911929"}}],"signature_block":"ARANg","raw_affiliations":[{"value":"Universidad Técnica Federico Santa María, Centro Científica y Tecnológica de Valparaíso-CCTVal, 2390123 Valparaíso, Chile","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Universidad Técnica Federico Santa María,2390123 Valparaíso,Chile"}]},{"uuid":"e53204e3-6e01-4090-8d0b-28c6e4631fce","record":{"$ref":"https://inspirehep.net/api/authors/1876577"},"full_name":"Volkov, P.V.","affiliations":[{"value":"SINP, Moscow","record":{"$ref":"https://inspirehep.net/api/institutions/908795"}},{"value":"Dubna, JINR","record":{"$ref":"https://inspirehep.net/api/institutions/902780"}},{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}}],"signature_block":"VALCAVp","raw_affiliations":[{"value":"Skobeltsyn Institute of Nuclear Physics, Lomonosov Moscow State University, 119991 Moscow, Russia","source":"APS"},{"value":"Joint Institute for Nuclear Research, 141980 Dubna, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Joint Institute for Nuclear Research,141980 Dubna,Russia"},{"value":"Skobeltsyn Institute of Nuclear Physics,Lomonosov Moscow State University,119991 Moscow,Russia"}]},{"uuid":"8b274e78-4aad-401b-bcfb-be652b05b914","record":{"$ref":"https://inspirehep.net/api/authors/1992553"},"full_name":"Volkov, V.Yu.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"SINP, Moscow","record":{"$ref":"https://inspirehep.net/api/institutions/908795"}}],"signature_block":"VALCAVv","raw_affiliations":[{"value":"Skobeltsyn Institute of Nuclear Physics, Lomonosov Moscow State University, 119991 Moscow, Russia","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Skobeltsyn Institute of Nuclear Physics,Lomonosov Moscow State University,119991 Moscow,Russia"}]},{"uuid":"d2841447-e76f-485a-8a67-20c5f7636dc0","record":{"$ref":"https://inspirehep.net/api/authors/1478928"},"full_name":"Ulloa, P.","affiliations":[{"value":"Valencia U., IFIC","record":{"$ref":"https://inspirehep.net/api/institutions/907907"}},{"value":"Andres Bello Natl. U.","record":{"$ref":"https://inspirehep.net/api/institutions/907969"}}],"signature_block":"ULp","curated_relation":true,"raw_affiliations":[{"value":"Departamento de Ciencias Físicas, Universidad Andres Bello, Sazié 2212, Piso 7, Santiago, Chile","source":"APS"},{"value":"Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia"},{"value":"Departamento de Ciencias Físicas,Universidad Andres Bello,Sazié2212,Piso 7,Santiago,Chile"}]}],"curated":true,"figures":[{"key":"1fa3b8447ad01f97a1956271a2479331","url":"https://inspirehep.net/files/1fa3b8447ad01f97a1956271a2479331","label":"fig:setup","source":"arxiv","caption":"The NA64 setup to search for $A'(a)\\to \\ee$ decays of the bremsstrahlung $A'(a)$ produced in the reaction $eZ \\to eZA'(a) $ of the 150 GeV electrons incident on the active WCAL target. The figure is reproduced from Ref. \\cite{visible-2018-analysis}.","filename":"setup_2018_vis.png","material":"preprint"},{"key":"ca453d4bc2b5979850ea4a5fca1943a6","url":"https://inspirehep.net/files/ca453d4bc2b5979850ea4a5fca1943a6","label":"fig:result","source":"arxiv","caption":"The 90\\% C.L. limits on the pseudoscalar particles decaying to $e^+e^-$ pairs. On the right vertical axis we use the standard notation for the pseudo-scalar coupling $\\xi_e=\\epsilon (V/m_e) \\sqrt{4 \\pi \\alpha_{QED}}$, where $V=246$~GeV is a vacuum expectation value of the Higgs field \\cite{Andreas:2010ms}. This corresponds to the Lagrangian term $\\mathcal{L}\\supset -i \\xi_e \\frac{m_e}{V} a \\bar{\\psi}_e \\gamma_5 \\psi_e$. The red vertical line corresponds to the ATOMKI anomaly at $m_a = 16.7$ MeV (central value of the first result on berillium). The $\\epsilon$ range excluded at this mass is $2.1\\times 10^{-4} < \\epsilon < 3.2\\times 10^{-4}$. The region excluded using only the data collected with the visible mode geometry is denoted as \"NA64 vis.\", the extention of this region obtained using all data is denoted as \"NA64 invis.\". The regions excluded by the $(g-2)_e$ measurements (Berkley \\cite{Parker191} and LKB \\cite{finestructure2020}) are shown. The limits from the electron beam-dump experiments E774~\\cite{bross} and Orsay~\\cite{dav} are taken from Ref.~\\cite{Andreas:2010ms}.","filename":"X_linear_Kirpich_visible_pseudoscalar.png","material":"preprint"}],"license":[{"url":"https://creativecommons.org/licenses/by/4.0/","license":"CC BY 4.0","material":"publication"},{"url":"http://creativecommons.org/licenses/by/4.0/","license":"CC BY 4.0","material":"preprint"}],"texkeys":["NA64:2021aiq","NA64:2021ked","Andreev:2021syk"],"citeable":true,"imprints":[{"date":"2021-12-01"}],"keywords":[{"value":"electron: beam","schema":"INSPIRE"},{"value":"new physics: search for","schema":"INSPIRE"},{"value":"new particle","schema":"INSPIRE"},{"value":"cross section: difference","schema":"INSPIRE"},{"value":"dimension: 2","schema":"INSPIRE"},{"value":"nucleus: transition","schema":"INSPIRE"},{"value":"statistics","schema":"INSPIRE"},{"value":"anomaly","schema":"INSPIRE"},{"value":"pseudoscalar particle: leptonic decay","schema":"INSPIRE"},{"value":"electron: pair production","schema":"INSPIRE"},{"value":"electron: coupling","schema":"INSPIRE"},{"value":"pseudoscalar particle: coupling","schema":"INSPIRE"},{"value":"pseudoscalar particle: mass","schema":"INSPIRE"},{"value":"pseudoscalar","schema":"INSPIRE"},{"value":"CERN SPS","schema":"INSPIRE"},{"value":"vector boson: mass","schema":"INSPIRE"},{"value":"accelerator","schema":"INSPIRE"},{"value":"efficiency","schema":"INSPIRE"},{"value":"axion-like particles","schema":"INSPIRE"},{"value":"sensitivity","schema":"INSPIRE"},{"value":"statistical analysis","schema":"INSPIRE"},{"value":"parameter space","schema":"INSPIRE"},{"value":"background","schema":"INSPIRE"},{"value":"lifetime: difference","schema":"INSPIRE"},{"value":"beryllium","schema":"INSPIRE"},{"value":"experimental results","schema":"INSPIRE"},{"value":"talk: Prague 2020/07/30","schema":"INSPIRE"},{"value":"dark matter: direct production","schema":"INSPIRE"},{"value":"gauge boson: postulated particle","schema":"INSPIRE"},{"value":"gauge boson: leptonic decay","schema":"INSPIRE"},{"value":"pseudoscalar particle","schema":"INSPIRE"},{"value":"data analysis method","schema":"INSPIRE"},{"value":"S029AXE","schema":"PDG"}],"refereed":true,"abstracts":[{"value":"We report the results of a search for a light pseudoscalar particle a that couples to electrons and decays to e+e− performed using the high-energy CERN SPS H4 electron beam. If such light pseudoscalar exists, it could explain the ATOMKI anomaly (an excess of e+e− pairs in the nuclear transitions of 8Be and 4He nuclei at the invariant mass ≃17  MeV observed by the experiment at the 5 MV Van de Graaff accelerator at ATOMKI, Hungary). We used the NA64 data collected in the “visible mode” configuration with a total statistics corresponding to 8.4×1010 electrons on target (EOT) in 2017 and 2018. In order to increase sensitivity to small coupling parameter ε we also used the data collected in 2016–2018 in the “invisible mode” configuration of NA64 with a total statistics corresponding to 2.84×1011 EOT. The background and efficiency estimates for these two configurations were retained from our previous analyses searching for light vector bosons and axionlike particles (ALP) (the latter were assumed to couple predominantly to γ). In this work we recalculate the signal yields, which are different due to different cross section and lifetime of a pseudoscalar particle a, and perform a new statistical analysis. As a result, the region of the two dimensional parameter space ma−ε in the mass range from 1 to 17.1 MeV is excluded. At the mass of the central value of the ATOMKI anomaly (the first result obtained on the beryllium nucleus, 16.7 MeV) the values of ε in the range 2.1×10−4<ε<3.2×10−4 are excluded.","source":"APS"},{"value":"We report the results of a search for a light pseudoscalar particle $a$ that couples to electrons and decays to $e^+e^-$ performed using the high-energy CERN SPS H4 electron beam. If such pseudoscalar with a mass $\\simeq 17$ MeV exists, it could explain the ATOMKI anomaly. We used the NA64 data samples collected in the \"visible mode\" configuration with total statistics corresponding to $8.4\\times 10^{10}$ electrons on target (EOT) in 2017 and 2018. In order to increase sensitivity to small coupling parameter $\\epsilon$ we used also the data collected in 2016-2018 in the \"invisible mode\" configuration of NA64 with a total statistics corresponding to $2.84\\times 10^{11}$ EOT. A thorough analysis of both these data samples in the sense of background and efficiency estimations was already performed and reported in our previous papers devoted to the search for light vector particles and axion-like particles (ALP). In this work we recalculate the signal yields, which are different due to different cross section and life time of a pseudoscalar particle $a$, and perform a new statistical analysis. As a result, the region of the two dimensional parameter space $m_a - \\epsilon$ in the mass range from 1 to 17.1 MeV is excluded. At the mass of the ATOMKI anomaly the values of $\\epsilon$ in the range $2.1 \\times 10^{-4} < \\epsilon < 3.2 \\times 10^{-4}$ are excluded.","source":"arXiv"}],"copyright":[{"year":2021,"holder":"authors","material":"publication"}],"references":[{"record":{"$ref":"https://inspirehep.net/api/literature/119084"},"raw_refs":[{"value":"1R. D. Peccei and H. R. Quinn, Phys. Rev. Lett. 38, 1440 (1977).PRLTAO0031-900710.1103/PhysRevLett.38.1440","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1103/PhysRevLett.38.1440"],"label":"1","authors":[{"full_name":"Peccei, R.D.","inspire_role":"author"},{"full_name":"Quinn, H.R.","inspire_role":"author"}],"publication_info":{"year":1977,"artid":"1440","page_start":"1440","journal_title":"Phys.Rev.Lett.","journal_record":{"$ref":"https://inspirehep.net/api/journals/1214495"},"journal_volume":"38"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/122138"},"raw_refs":[{"value":"2S. Weinberg, Phys. Rev. Lett. 40, 223 (1978).PRLTAO0031-900710.1103/PhysRevLett.40.223","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1103/PhysRevLett.40.223"],"label":"2","authors":[{"full_name":"Weinberg, S.","inspire_role":"author"}],"publication_info":{"year":1978,"artid":"223","page_start":"223","journal_title":"Phys.Rev.Lett.","journal_record":{"$ref":"https://inspirehep.net/api/journals/1214495"},"journal_volume":"40"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/5997"},"raw_refs":[{"value":"3F. Wilczek, Phys. Rev. Lett. 40, 279 (1978).PRLTAO0031-900710.1103/PhysRevLett.40.279","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1103/PhysRevLett.40.279"],"label":"3","authors":[{"full_name":"Wilczek, F.","inspire_role":"author"}],"publication_info":{"year":1978,"artid":"279","page_start":"279","journal_title":"Phys.Rev.Lett.","journal_record":{"$ref":"https://inspirehep.net/api/journals/1214495"},"journal_volume":"40"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/1263039"},"raw_refs":[{"value":"4R. Essig , arXiv:1311.0029.","schema":"JATS","source":"APS"}],"reference":{"label":"4","authors":[{"full_name":"Essig, R.","inspire_role":"author"}],"arxiv_eprint":"1311.0029"}},{"record":{"$ref":"https://inspirehep.net/api/literature/1484628"},"raw_refs":[{"value":"5J. Alexander , arXiv:1608.08632.","schema":"JATS","source":"APS"}],"reference":{"label":"5","authors":[{"full_name":"Alexander, J.","inspire_role":"author"}],"arxiv_eprint":"1608.08632"}},{"record":{"$ref":"https://inspirehep.net/api/literature/1610250"},"raw_refs":[{"value":"6M. Battaglieri , arXiv:1707.04591.","schema":"JATS","source":"APS"}],"reference":{"label":"6","authors":[{"full_name":"Battaglieri, M.","inspire_role":"author"}],"arxiv_eprint":"1707.04591"}},{"record":{"$ref":"https://inspirehep.net/api/literature/1717494"},"raw_refs":[{"value":"7J. Beacham , J. Phys. G 47, 010501 (2020).JPGPED0954-389910.1088/1361-6471/ab4cd2","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1088/1361-6471/ab4cd2"],"label":"7","authors":[{"full_name":"Beacham, J.","inspire_role":"author"}],"publication_info":{"year":2020,"artid":"010501","journal_title":"J.Phys.G","journal_record":{"$ref":"https://inspirehep.net/api/journals/1613989"},"journal_volume":"47"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/1761133"},"raw_refs":[{"value":"8R. K. Ellis , arXiv:1910.11775.","schema":"JATS","source":"APS"}],"reference":{"label":"8","authors":[{"full_name":"Ellis, R.K.","inspire_role":"author"}],"arxiv_eprint":"1910.11775"}},{"record":{"$ref":"https://inspirehep.net/api/literature/1681017"},"raw_refs":[{"value":"9A. Berlin, N. Blinov, G. Krnjaic, P. Schuster, and N. Toro, Phys. Rev. D 99, 075001 (2019).PRVDAQ2470-001010.1103/PhysRevD.99.075001","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1103/PhysRevD.99.075001"],"label":"9","authors":[{"full_name":"Berlin, A.","inspire_role":"author"},{"full_name":"Blinov, N.","inspire_role":"author"},{"full_name":"Krnjaic, G.","inspire_role":"author"},{"full_name":"Schuster, P.","inspire_role":"author"},{"full_name":"Toro, N.","inspire_role":"author"}],"publication_info":{"year":2019,"artid":"075001","journal_title":"Phys.Rev.D","journal_record":{"$ref":"https://inspirehep.net/api/journals/1613970"},"journal_volume":"99"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/1801701"},"raw_refs":[{"value":"10E. Aprile , Phys. Rev. D 102, 072004 (2020).PRVDAQ2470-001010.1103/PhysRevD.102.072004","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1103/PhysRevD.102.072004"],"label":"10","authors":[{"full_name":"Aprile, E.","inspire_role":"author"}],"publication_info":{"year":2020,"artid":"072004","journal_title":"Phys.Rev.D","journal_record":{"$ref":"https://inspirehep.net/api/journals/1613970"},"journal_volume":"102"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/1831044"},"raw_refs":[{"value":"11D. Buttazzo, P. Panci, D. Teresi, and R. Ziegler, Phys. Lett. B 817, 136310 (2021).PYLBAJ0370-269310.1016/j.physletb.2021.136310","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1016/j.physletb.2021.136310"],"label":"11","authors":[{"full_name":"Buttazzo, D.","inspire_role":"author"},{"full_name":"Panci, P.","inspire_role":"author"},{"full_name":"Teresi, D.","inspire_role":"author"},{"full_name":"Ziegler, R.","inspire_role":"author"}],"publication_info":{"year":2021,"artid":"136310","journal_title":"Phys.Lett.B","journal_record":{"$ref":"https://inspirehep.net/api/journals/1613966"},"journal_volume":"817"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/1816996"},"raw_refs":[{"value":"12D. S. Alves, Phys. Rev. D 103, 055018 (2021).PRVDAQ2470-001010.1103/PhysRevD.103.055018","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1103/PhysRevD.103.055018"],"label":"12","authors":[{"full_name":"Alves, D.S.","inspire_role":"author"}],"publication_info":{"year":2021,"artid":"055018","journal_title":"Phys.Rev.D","journal_record":{"$ref":"https://inspirehep.net/api/journals/1613970"},"journal_volume":"103"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/1485381"},"raw_refs":[{"value":"13U. Ellwanger and S. Moretti, J. High Energy Phys. 11 (2016) 039.JHEPFG1029-847910.1007/JHEP11(2016)039","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1007/JHEP11(2016)039"],"label":"13","authors":[{"full_name":"Ellwanger, U.","inspire_role":"author"},{"full_name":"Moretti, S.","inspire_role":"author"}],"publication_info":{"artid":"039","page_start":"039","journal_issue":"11","journal_title":"J. High Energy Phys.","journal_volume":"2016"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/1358248"},"raw_refs":[{"value":"14A. J. Krasznahorkay , Phys. Rev. Lett. 116, 042501 (2016).PRLTAO0031-900710.1103/PhysRevLett.116.042501","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1103/PhysRevLett.116.042501"],"label":"14","authors":[{"full_name":"Krasznahorkay, A.J.","inspire_role":"author"}],"publication_info":{"year":2016,"artid":"042501","journal_title":"Phys.Rev.Lett.","journal_record":{"$ref":"https://inspirehep.net/api/journals/1214495"},"journal_volume":"116"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/1760389"},"raw_refs":[{"value":"15A. Krasznahorkay , arXiv:1910.10459.","schema":"JATS","source":"APS"}],"reference":{"label":"15","authors":[{"full_name":"Krasznahorkay, A.","inspire_role":"author"}],"arxiv_eprint":"1910.10459"}},{"record":{"$ref":"https://inspirehep.net/api/literature/1859286"},"raw_refs":[{"value":"16A. J. Krasznahorkay, M. Csatlós, L. Csige, J. Gulyás, A. Krasznahorkay, B. M. Nyakó, I. Rajta, J. Timár, I. Vajda, and N. J. Sas, Phys. Rev. C 104, 044003 (2021).PRVCAN2469-998510.1103/PhysRevC.104.044003","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1103/PhysRevC.104.044003"],"label":"16","authors":[{"full_name":"Krasznahorkay, A.J.","inspire_role":"author"},{"full_name":"Csatlós, M.","inspire_role":"author"},{"full_name":"Csige, L.","inspire_role":"author"},{"full_name":"Gulyás, J.","inspire_role":"author"},{"full_name":"Krasznahorkay, A.","inspire_role":"author"},{"full_name":"Nyakó, B.M.","inspire_role":"author"},{"full_name":"Rajta, I.","inspire_role":"author"},{"full_name":"Timár, J.","inspire_role":"author"},{"full_name":"Vajda, I.","inspire_role":"author"},{"full_name":"Sas, N.J.","inspire_role":"author"}],"publication_info":{"year":2021,"artid":"044003","journal_title":"Phys.Rev.C","journal_record":{"$ref":"https://inspirehep.net/api/journals/1613969"},"journal_volume":"104"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/1708623"},"raw_refs":[{"value":"17R. H. Parker, C. Yu, W. Zhong, B. Estey, and H. Müller, Science 360, 191 (2018).SCIEAS0036-807510.1126/science.aap7706","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1126/science.aap7706"],"label":"17","authors":[{"full_name":"Parker, R.H.","inspire_role":"author"},{"full_name":"Yu, C.","inspire_role":"author"},{"full_name":"Zhong, W.","inspire_role":"author"},{"full_name":"Estey, B.","inspire_role":"author"},{"full_name":"Müller, H.","inspire_role":"author"}],"publication_info":{"year":2018,"artid":"191","page_start":"191","journal_title":"Science","journal_record":{"$ref":"https://inspirehep.net/api/journals/1214406"},"journal_volume":"360"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/1837309"},"raw_refs":[{"value":"18L. Morel, Z. Yao, P. Cladé, and S. Guellati-Khélifa, Nature (London) 588, 61 (2020).NATUAS0028-083610.1038/s41586-020-2964-7","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1038/s41586-020-2964-7"],"label":"18","authors":[{"full_name":"Morel, L.","inspire_role":"author"},{"full_name":"Yao, Z.","inspire_role":"author"},{"full_name":"Cladé, P.","inspire_role":"author"},{"full_name":"Guellati-Khélifa, S.","inspire_role":"author"}],"publication_info":{"year":2020,"artid":"61","page_start":"61","journal_title":"Nature","journal_record":{"$ref":"https://inspirehep.net/api/journals/1214565"},"journal_volume":"588"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/1844568"},"raw_refs":[{"value":"19Y. M. Andreev , Phys. Rev. Lett. 126, 211802 (2021).PRLTAO0031-900710.1103/PhysRevLett.126.211802","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1103/PhysRevLett.126.211802"],"label":"19","authors":[{"full_name":"Andreev, Y.M.","inspire_role":"author"}],"publication_info":{"year":2021,"artid":"211802","journal_title":"Phys.Rev.Lett.","journal_record":{"$ref":"https://inspirehep.net/api/journals/1214495"},"journal_volume":"126"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/1773005"},"raw_refs":[{"value":"20D. Banerjee , Phys. Rev. D 101, 071101 (2020).PRVDAQ2470-001010.1103/PhysRevD.101.071101","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1103/PhysRevD.101.071101"],"label":"20","authors":[{"full_name":"Banerjee, D.","inspire_role":"author"}],"publication_info":{"year":2020,"artid":"071101","journal_title":"Phys.Rev.D","journal_record":{"$ref":"https://inspirehep.net/api/journals/1613970"},"journal_volume":"101"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/1663446"},"raw_refs":[{"value":"21D. Banerjee , Phys. Rev. Lett. 120, 231802 (2018).PRLTAO0031-900710.1103/PhysRevLett.120.231802","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1103/PhysRevLett.120.231802"],"label":"21","authors":[{"full_name":"Banerjee, D.","inspire_role":"author"}],"publication_info":{"year":2018,"artid":"231802","journal_title":"Phys.Rev.Lett.","journal_record":{"$ref":"https://inspirehep.net/api/journals/1214495"},"journal_volume":"120"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/1628408"},"raw_refs":[{"value":"22D. Banerjee , Phys. Rev. D 97, 072002 (2018).PRVDAQ2470-001010.1103/PhysRevD.97.072002","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1103/PhysRevD.97.072002"],"label":"22","authors":[{"full_name":"Banerjee, D.","inspire_role":"author"}],"publication_info":{"year":2018,"artid":"072002","journal_title":"Phys.Rev.D","journal_record":{"$ref":"https://inspirehep.net/api/journals/1613970"},"journal_volume":"97"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/1598132"},"raw_refs":[{"value":"23Y.-S. Liu and G. A. Miller, Phys. Rev. D 96, 016004 (2017).PRVDAQ2470-001010.1103/PhysRevD.96.016004","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1103/PhysRevD.96.016004"],"label":"23","authors":[{"full_name":"Liu, Y.-S.","inspire_role":"author"},{"full_name":"Miller, G.A.","inspire_role":"author"}],"publication_info":{"year":2017,"artid":"016004","journal_title":"Phys.Rev.D","journal_record":{"$ref":"https://inspirehep.net/api/journals/1613970"},"journal_volume":"96"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/1737728"},"raw_refs":[{"value":"24D. Banerjee , Phys. Rev. Lett. 123, 121801 (2019).PRLTAO0031-900710.1103/PhysRevLett.123.121801","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1103/PhysRevLett.123.121801"],"label":"24","authors":[{"full_name":"Banerjee, D.","inspire_role":"author"}],"publication_info":{"year":2019,"artid":"121801","journal_title":"Phys.Rev.Lett.","journal_record":{"$ref":"https://inspirehep.net/api/journals/1214495"},"journal_volume":"123"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/1794463"},"raw_refs":[{"value":"25D. Banerjee , Phys. Rev. Lett. 125, 081801 (2020).PRLTAO0031-900710.1103/PhysRevLett.125.081801","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1103/PhysRevLett.125.081801"],"label":"25","authors":[{"full_name":"Banerjee, D.","inspire_role":"author"}],"publication_info":{"year":2020,"artid":"081801","journal_title":"Phys.Rev.Lett.","journal_record":{"$ref":"https://inspirehep.net/api/journals/1214495"},"journal_volume":"125"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/593382"},"raw_refs":[{"value":"26S. Agostinelli , Nucl. Instrum. Methods Phys. Res., Sect. A 506, 250 (2003).NIMAER0168-900210.1016/S0168-9002(03)01368-8","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1016/S0168-9002(03)01368-8"],"label":"26","authors":[{"full_name":"Agostinelli, S.","inspire_role":"author"}],"publication_info":{"year":2003,"artid":"250","page_start":"250","journal_title":"Nucl.Instrum.Meth.A","journal_record":{"$ref":"https://inspirehep.net/api/journals/1613981"},"journal_volume":"506"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/1843224"},"raw_refs":[{"value":"27M. Bondi, A. Celentano, R. R. Dusaev, D. V. Kirpichnikov, M. M. Kirsanov, N. V. Krasnikov, L. Marsicano, and D. Shchukin, Comput. Phys. Commun. 269, 108129 (2021).CPHCBZ0010-465510.1016/j.cpc.2021.108129","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1016/j.cpc.2021.108129"],"label":"27","authors":[{"full_name":"Bondi, M.","inspire_role":"author"},{"full_name":"Celentano, A.","inspire_role":"author"},{"full_name":"Dusaev, R.R.","inspire_role":"author"},{"full_name":"Kirpichnikov, D.V.","inspire_role":"author"},{"full_name":"Kirsanov, M.M.","inspire_role":"author"},{"full_name":"Krasnikov, N.V.","inspire_role":"author"},{"full_name":"Marsicano, L.","inspire_role":"author"},{"full_name":"Shchukin, D.","inspire_role":"author"}],"publication_info":{"year":2021,"artid":"108129","journal_title":"Comput.Phys.Commun.","journal_record":{"$ref":"https://inspirehep.net/api/journals/1214781"},"journal_volume":"269"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/897661"},"raw_refs":[{"value":"28I. Antcheva , Comput. Phys. Commun. 182, 1384 (2011).CPHCBZ0010-465510.1016/j.cpc.2011.02.008","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1016/j.cpc.2011.02.008"],"label":"28","authors":[{"full_name":"Antcheva, I.","inspire_role":"author"}],"publication_info":{"year":2011,"artid":"1384","page_start":"1384","journal_title":"Comput.Phys.Commun.","journal_record":{"$ref":"https://inspirehep.net/api/journals/1214781"},"journal_volume":"182"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/494953"},"raw_refs":[{"value":"29T. Junk, Nucl. Instrum. Methods Phys. Res., Sect. A 434, 435 (1999).NIMAER0168-900210.1016/S0168-9002(99)00498-2","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1016/S0168-9002(99)00498-2"],"label":"29","authors":[{"full_name":"Junk, T.","inspire_role":"author"}],"publication_info":{"year":1999,"artid":"435","page_start":"435","journal_title":"Nucl.Instrum.Meth.A","journal_record":{"$ref":"https://inspirehep.net/api/journals/1613981"},"journal_volume":"434"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/860907"},"raw_refs":[{"value":"30G. Cowan, K. Cranmer, E. Gross, and O. Vitells, Eur. Phys. J. C 71, 1554 (2011).EPCFFB1434-604410.1140/epjc/s10052-011-1554-0","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1140/epjc/s10052-011-1554-0"],"label":"30","authors":[{"full_name":"Cowan, G.","inspire_role":"author"},{"full_name":"Cranmer, K.","inspire_role":"author"},{"full_name":"Gross, E.","inspire_role":"author"},{"full_name":"Vitells, O.","inspire_role":"author"}],"publication_info":{"year":2011,"artid":"1554","page_start":"1554","journal_title":"Eur.Phys.J.C","journal_record":{"$ref":"https://inspirehep.net/api/journals/1613946"},"journal_volume":"71"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/599622"},"raw_refs":[{"value":"31A. L. Read, J. Phys. G 28, 2693 (2002).JPGPED0954-389910.1088/0954-3899/28/10/313","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1088/0954-3899/28/10/313"],"label":"31","authors":[{"full_name":"Read, A.L.","inspire_role":"author"}],"publication_info":{"year":2002,"artid":"2693","page_start":"2693","journal_title":"J.Phys.G","journal_record":{"$ref":"https://inspirehep.net/api/journals/1613989"},"journal_volume":"28"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/1629957"},"raw_refs":[{"value":"32D. S. M. Alves and N. Weiner, J. High Energy Phys. 07 (2018) 092.JHEPFG1029-847910.1007/JHEP07(2018)092","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1007/JHEP07(2018)092"],"label":"32","authors":[{"full_name":"Alves, D.S.M.","inspire_role":"author"},{"full_name":"Weiner, N.","inspire_role":"author"}],"publication_info":{"artid":"092","page_start":"092","journal_issue":"07","journal_title":"J. High Energy Phys.","journal_volume":"2018"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/855941"},"raw_refs":[{"value":"33S. Andreas, O. Lebedev, S. Ramos-Sánchez, and A. Ringwald, J. High Energy Phys. 08 (2010) 003.JHEPFG1029-847910.1007/JHEP08(2010)003","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1007/JHEP08(2010)003"],"label":"33","authors":[{"full_name":"Andreas, S.","inspire_role":"author"},{"full_name":"Lebedev, O.","inspire_role":"author"},{"full_name":"Ramos-Sánchez, S.","inspire_role":"author"},{"full_name":"Ringwald, A.","inspire_role":"author"}],"publication_info":{"artid":"003","page_start":"003","journal_issue":"08","journal_title":"J. High Energy Phys.","journal_volume":"2010"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/280603"},"raw_refs":[{"value":"34A. Bross, M. Crisler, S. Pordes, J. Volk, S. Errede, and J. Wrbanek, Phys. Rev. Lett. 67, 2942 (1991).PRLTAO0031-900710.1103/PhysRevLett.67.2942","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1103/PhysRevLett.67.2942"],"label":"34","authors":[{"full_name":"Bross, A.","inspire_role":"author"},{"full_name":"Crisler, M.","inspire_role":"author"},{"full_name":"Pordes, S.","inspire_role":"author"},{"full_name":"Volk, J.","inspire_role":"author"},{"full_name":"Errede, S.","inspire_role":"author"},{"full_name":"Wrbanek, J.","inspire_role":"author"}],"publication_info":{"year":1991,"artid":"2942","page_start":"2942","journal_title":"Phys.Rev.Lett.","journal_record":{"$ref":"https://inspirehep.net/api/journals/1214495"},"journal_volume":"67"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/282960"},"raw_refs":[{"value":"35M. Davier and H. N. Ngoc, Phys. Lett. B 229, 150 (1989).PYLBAJ0370-269310.1016/0370-2693(89)90174-3","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1016/0370-2693(89)90174-3"],"label":"35","authors":[{"full_name":"Davier, M.","inspire_role":"author"},{"full_name":"Ngoc, H.N.","inspire_role":"author"}],"publication_info":{"year":1989,"artid":"150","page_start":"150","journal_title":"Phys.Lett.B","journal_record":{"$ref":"https://inspirehep.net/api/journals/1613966"},"journal_volume":"229"}}},{"record":{"$ref":"https://inspirehep.net/api/literature/1815457"},"raw_refs":[{"value":"36E. Depero , Eur. Phys. J. C 80, 1159 (2020).EPCFFB1434-604410.1140/epjc/s10052-020-08725-x","schema":"JATS","source":"APS"}],"reference":{"dois":["10.1140/epjc/s10052-020-08725-x"],"label":"36","authors":[{"full_name":"Depero, E.","inspire_role":"author"}],"publication_info":{"year":2020,"artid":"1159","page_start":"1159","journal_title":"Eur.Phys.J.C","journal_record":{"$ref":"https://inspirehep.net/api/journals/1613946"},"journal_volume":"80"}}}],"public_notes":[{"value":"6 pages, 2 figures","source":"arXiv"}],"arxiv_eprints":[{"value":"2104.13342","categories":["hep-ex"]}],"document_type":["article","conference paper"],"preprint_date":"2021-04-27","collaborations":[{"value":"NA64","record":{"$ref":"https://inspirehep.net/api/experiments/1479798"}}],"control_number":1992535,"legacy_version":"20210526164834.0","deleted_records":[{"$ref":"https://inspirehep.net/api/literature/1860981"}],"number_of_pages":5,"inspire_categories":[{"term":"Experiment-HEP","source":"curator"},{"term":"Experiment-HEP","source":"arxiv"},{"term":"Experiment-HEP"}],"legacy_creation_date":"2021-04-28","accelerator_experiments":[{"record":{"$ref":"https://inspirehep.net/api/experiments/1479798"},"legacy_name":"CERN-NA-064"}],"external_system_identifiers":[{"value":"2765541","schema":"CDS"},{"value":"2798711","schema":"CDS"}]},"uuid":"efbeb951-5ef3-4399-8d9a-0a2ce7a5e8c5","revision_id":29,"created":"2021-04-28T00:00:00+00:00","updated":"2025-06-05T07:26:20.266932+00:00","id":"1992535","links":{"bibtex":"https://inspirehep.net/api/literature/1992535?format=bibtex","latex-eu":"https://inspirehep.net/api/literature/1992535?format=latex-eu","latex-us":"https://inspirehep.net/api/literature/1992535?format=latex-us","json":"https://inspirehep.net/api/literature/1992535?format=json","json-expanded":"https://inspirehep.net/api/literature/1992535?format=json-expanded","cv":"https://inspirehep.net/api/literature/1992535?format=cv","citations":"https://inspirehep.net/api/literature/?q=refersto%3Arecid%3A1992535"}} + ] + } +} \ No newline at end of file diff --git a/site/tests/inspire_harvester/data/record_with_erratum_DOI.json b/site/tests/inspire_harvester/data/record_with_erratum_DOI.json new file mode 100644 index 00000000..41eb9827 --- /dev/null +++ b/site/tests/inspire_harvester/data/record_with_erratum_DOI.json @@ -0,0 +1,563 @@ +{ + "hits": { + "total": 1, + "hits": [ + { + "links": { + "bibtex": "https://inspirehep.net/api/literature/1938038?format=bibtex", + "latex-eu": "https://inspirehep.net/api/literature/1938038?format=latex-eu", + "latex-us": "https://inspirehep.net/api/literature/1938038?format=latex-us", + "json": "https://inspirehep.net/api/literature/1938038?format=json", + "json-expanded": "https://inspirehep.net/api/literature/1938038?format=json-expanded", + "cv": "https://inspirehep.net/api/literature/1938038?format=cv", + "citations": "https://inspirehep.net/api/literature/?q=refersto%3Arecid%3A1938038" + }, + "revision_id": 15, + "updated": "2024-05-07T10:56:12.709267+00:00", + "metadata": { + "citation_count_without_self_citations": 8, + "publication_info": [ + { + "year": 2021, + "artid": "277", + "page_start": "277", + "journal_issue": "9", + "journal_title": "Eur.Phys.J.A", + "journal_record": { + "$ref": "https://inspirehep.net/api/journals/1613944" + }, + "journal_volume": "57" + }, + { + "year": 2021, + "artid": "277", + "material": "publication", + "page_start": "277", + "journal_issue": "9", + "journal_title": "Eur.Phys.J.A", + "journal_record": { + "$ref": "https://inspirehep.net/api/journals/1613944" + }, + "journal_volume": "57" + }, + { + "year": 2021, + "artid": "306", + "material": "erratum", + "page_start": "306", + "journal_issue": "11", + "journal_title": "Eur.Phys.J.A", + "journal_record": { + "$ref": "https://inspirehep.net/api/journals/1613944" + }, + "journal_volume": "57", + "curated_relation": true + } + ], + "citation_count": 10, + "documents": [ + { + "key": "e6e303046c0c510249cde1e4d535b267", + "url": "https://inspirehep.net/files/e6e303046c0c510249cde1e4d535b267", + "filename": "Michalopoulou2021_Article_MeasurementOfThe232232ThNFCros.pdf", + "fulltext": true, + "description": "Fulltext" + } + ], + "core": true, + "dois": [ + { + "value": "10.1140/epja/s10050-021-00590-w", + "source": "Springer" + }, + { + "value": "10.1140/epja/s10050-021-00613-6", + "source": "Springer", + "material": "erratum" + }, + { + "value": "10.1140/epja/s10050-021-00590-w" + } + ], + "titles": [ + { + "title": "Measurement of the $^{232}$Th(n,f) cross section with quasi-monoenergetic neutron beams in the energy range 2–18 MeV", + "source": "Springer" + }, + { + "title": "Measurement of the $^{232}$Th(n,f) cross section with quasi-monoenergetic neutron beams in the energy range 2–18 MeV", + "source": "submitter" + } + ], + "$schema": "https://inspirehep.net/schemas/records/hep.json", + "authors": [ + { + "uuid": "e65b5d39-1d64-4de4-880a-34a0d4aa2d13", + "emails": [ + "veatriki.michalopoulou@cern.ch" + ], + "record": { + "$ref": "https://inspirehep.net/api/authors/1938039" + }, + "full_name": "Michalopoulou, V.", + "affiliations": [ + { + "value": "CERN", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902725" + } + }, + { + "value": "Natl. Tech. U., Athens", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903697" + }, + "curated_relation": true + } + ], + "signature_block": "MACALAPALv", + "raw_affiliations": [ + { + "value": "Department of Physics, National Technical University of Athens, Zografou Campus, Athens, Greece" + }, + { + "value": "European Organisation for Nuclear Research (CERN), Geneva, Switzerland" + } + ], + "affiliations_identifiers": [ + { + "value": "grid.4241.3", + "schema": "GRID" + }, + { + "value": "grid.9132.9", + "schema": "GRID" + } + ] + }, + { + "uuid": "8466afb0-7135-4da5-b3f9-46baecbb6742", + "record": { + "$ref": "https://inspirehep.net/api/authors/1938042" + }, + "full_name": "Axiotis, M.", + "affiliations": [ + { + "value": "Democritos Nucl. Res. Ctr.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902768" + } + } + ], + "signature_block": "AXATm", + "raw_affiliations": [ + { + "value": "Tandem Accelerator Laboratory, Institute of Nuclear and Particle Physics, N.C.S.R. “Demokritos”, Aghia Paraskevi, Athens, Greece" + } + ], + "affiliations_identifiers": [ + { + "value": "grid.450262.7", + "schema": "GRID" + } + ] + }, + { + "uuid": "127bac84-1cc6-4ca1-898e-c5861cc58345", + "record": { + "$ref": "https://inspirehep.net/api/authors/1938043" + }, + "full_name": "Chasapoglou, S.", + "affiliations": [ + { + "value": "Natl. Tech. U., Athens", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903697" + } + } + ], + "signature_block": "CASAPAGLs", + "raw_affiliations": [ + { + "value": "Department of Physics, National Technical University of Athens, Zografou Campus, Athens, Greece" + } + ], + "affiliations_identifiers": [ + { + "value": "grid.4241.3", + "schema": "GRID" + } + ] + }, + { + "uuid": "a13d798b-0e79-4156-9c23-4b5282efac55", + "record": { + "$ref": "https://inspirehep.net/api/authors/1966768" + }, + "full_name": "Eleme, Z.", + "affiliations": [ + { + "value": "Ioannina U.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902891" + } + } + ], + "signature_block": "ELANz", + "raw_affiliations": [ + { + "value": "Department of Physics, University of Ioannina, Ioannina, Greece" + } + ], + "affiliations_identifiers": [ + { + "value": "grid.9594.1", + "schema": "GRID" + } + ] + }, + { + "uuid": "a07c2744-865b-47df-b2c8-9e876ae6b8fd", + "record": { + "$ref": "https://inspirehep.net/api/authors/1938044" + }, + "full_name": "Gkatis, G.", + "affiliations": [ + { + "value": "Natl. Tech. U., Athens", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903697" + } + } + ], + "signature_block": "GCATg", + "raw_affiliations": [ + { + "value": "Department of Physics, National Technical University of Athens, Zografou Campus, Athens, Greece" + } + ], + "affiliations_identifiers": [ + { + "value": "grid.4241.3", + "schema": "GRID" + } + ] + }, + { + "uuid": "b36d3f81-7c84-4eed-9405-38d8ad51c111", + "record": { + "$ref": "https://inspirehep.net/api/authors/1914385" + }, + "full_name": "Kalamara, A.", + "affiliations": [ + { + "value": "Natl. Tech. U., Athens", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903697" + } + }, + { + "value": "Democritos Nucl. Res. Ctr.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902768" + }, + "curated_relation": true + } + ], + "signature_block": "CALANARa", + "raw_affiliations": [ + { + "value": "Department of Physics, National Technical University of Athens, Zografou Campus, Athens, Greece" + }, + { + "value": "Institute of Nuclear and Radiological Sciences, Technology, Energy and Safety, N.C.S.R. “Demokritos”, Aghia Paraskevi, Athens, Greece" + }, + { + "value": "Institute of Nanoscience and Nanotechnology, N.C.S.R. “Demokritos”, Aghia Paraskevi, Athens, Greece" + } + ], + "affiliations_identifiers": [ + { + "value": "grid.4241.3", + "schema": "GRID" + }, + { + "value": "grid.450262.7", + "schema": "GRID" + } + ] + }, + { + "uuid": "2e7ed378-3c58-44d5-b57c-ceda23ed0ec3", + "record": { + "$ref": "https://inspirehep.net/api/authors/1914387" + }, + "full_name": "Kokkoris, M.", + "affiliations": [ + { + "value": "Natl. Tech. U., Athens", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903697" + } + } + ], + "signature_block": "CACARm", + "raw_affiliations": [ + { + "value": "Department of Physics, National Technical University of Athens, Zografou Campus, Athens, Greece" + } + ], + "affiliations_identifiers": [ + { + "value": "grid.4241.3", + "schema": "GRID" + } + ] + }, + { + "uuid": "78919089-b403-4913-94d1-95f9f0d09960", + "record": { + "$ref": "https://inspirehep.net/api/authors/1938045" + }, + "full_name": "Lagoyannis, A.", + "affiliations": [ + { + "value": "Democritos Nucl. Res. Ctr.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902768" + } + } + ], + "signature_block": "LAGYANa", + "raw_affiliations": [ + { + "value": "Tandem Accelerator Laboratory, Institute of Nuclear and Particle Physics, N.C.S.R. “Demokritos”, Aghia Paraskevi, Athens, Greece" + } + ], + "affiliations_identifiers": [ + { + "value": "grid.450262.7", + "schema": "GRID" + } + ] + }, + { + "uuid": "96026a22-2e7e-45bb-9573-3027ede5a76f", + "record": { + "$ref": "https://inspirehep.net/api/authors/1063025" + }, + "full_name": "Patronis, N.", + "affiliations": [ + { + "value": "Ioannina U.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902891" + } + } + ], + "signature_block": "PATRANn", + "raw_affiliations": [ + { + "value": "Department of Physics, University of Ioannina, Ioannina, Greece" + } + ], + "affiliations_identifiers": [ + { + "value": "grid.9594.1", + "schema": "GRID" + } + ] + }, + { + "uuid": "3451f865-0a02-420f-92ce-b20e4aad2414", + "record": { + "$ref": "https://inspirehep.net/api/authors/1914404" + }, + "full_name": "Stamatopoulos, A.", + "affiliations": [ + { + "value": "Natl. Tech. U., Athens", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903697" + } + } + ], + "signature_block": "STANATAPALa", + "raw_affiliations": [ + { + "value": "Department of Physics, National Technical University of Athens, Zografou Campus, Athens, Greece" + } + ], + "affiliations_identifiers": [ + { + "value": "grid.4241.3", + "schema": "GRID" + } + ] + }, + { + "uuid": "2b35bf75-ec7a-4429-b21d-53f70bad82f3", + "record": { + "$ref": "https://inspirehep.net/api/authors/1938046" + }, + "full_name": "Tsantiri, A.", + "affiliations": [ + { + "value": "Natl. Tech. U., Athens", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903697" + } + } + ], + "signature_block": "TSANTARa", + "raw_affiliations": [ + { + "value": "Department of Physics, National Technical University of Athens, Zografou Campus, Athens, Greece" + } + ], + "affiliations_identifiers": [ + { + "value": "grid.4241.3", + "schema": "GRID" + } + ] + }, + { + "uuid": "8fb19844-4618-4372-b6d2-4f506b127ae7", + "record": { + "$ref": "https://inspirehep.net/api/authors/1914409" + }, + "full_name": "Vlastou, R.", + "affiliations": [ + { + "value": "Natl. Tech. U., Athens", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903697" + } + } + ], + "signature_block": "VLASTr", + "raw_affiliations": [ + { + "value": "Department of Physics, National Technical University of Athens, Zografou Campus, Athens, Greece" + } + ], + "affiliations_identifiers": [ + { + "value": "grid.4241.3", + "schema": "GRID" + } + ] + } + ], + "curated": true, + "license": [ + { + "url": "http://creativecommons.org/licenses/by/4.0/", + "license": "CC-BY-4.0", + "imposing": "Springer" + }, + { + "url": "http://creativecommons.org/licenses/by/4.0/", + "license": "CC-BY-.0" + } + ], + "texkeys": [ + "Michalopoulou:2021qjq" + ], + "citeable": true, + "imprints": [ + { + "date": "2021-11-08" + }, + { + "date": "2021-09-27" + } + ], + "keywords": [ + { + "value": "n: beam", + "schema": "INSPIRE" + }, + { + "value": "n: energy", + "schema": "INSPIRE" + }, + { + "value": "numerical calculations: Monte Carlo", + "schema": "INSPIRE" + }, + { + "value": "fission", + "schema": "INSPIRE" + }, + { + "value": "Micromegas", + "schema": "INSPIRE" + }, + { + "value": "cross section", + "schema": "INSPIRE" + }, + { + "value": "experimental results", + "schema": "INSPIRE" + }, + { + "value": "FLUKA", + "schema": "INSPIRE" + } + ], + "refereed": true, + "abstracts": [ + { + "value": "The fission cross section of $^{232}$Th has been measured at fast neutron energies, using a setup based on Micromegas detectors. The experiment was performed at the 5.5 MV Van de Graaff Tandem accelerator in the neutron beam facility of the National Centre for Scientific Research “Demokritos”. The quasi-monoenergetic neutron beams were produced via the $^{3}$H(p,n), $^{2}$H(d,n) and $^{3}$H(d,n) reactions, while the $^{238}$U(n,f) and $^{235}$U(n,f) reactions were used as references, in order to acquire cross-section data points in the energy range 2–18 MeV. The characterization of the actinide samples was performed via $\\alpha $-spectroscopy with a Silicon Surface Barrier (SSB) detector, while Monte Carlo simulations with the FLUKA code were used to achieve the deconvolution of the $^{232}$Th $\\alpha $ peak from the $\\alpha $ background of its daughter nuclei present in the spectrum. Special attention was given to the study of the parasitic neutrons present in the experimental area, produced via charged particle reactions induced by the particle beam and from neutron scattering. Details on the data analysis and results are presented.", + "source": "Springer" + }, + { + "value": "The fission cross section of $^{232}$Th has been measured at fast neutron energies, using a setup based on Micromegas detectors. The experiment was performed at the 5.5 MV Van de Graaff Tandem accelerator in the neutron beam facility of the National Centre for Scientific Research “Demokritos”. The quasi-monoenergetic neutron beams were produced via the $^3$H(p,n), $^2$H(d,n) and $^3$H(d,n) reactions, while the $^{238}$U(n,f) and $^{235}$U(n,f) reactions were used as references, in order to acquire cross-section data points in the energy range 2–18 MeV. The characterization of the actinide samples was performed via α-spectroscopy with a Silicon Surface Barrier (SSB) detector, while Monte Carlo simulations with the FLUKA code were used to achieve the deconvolution of the $^{232}$Th $\\alpha$ peak from the α background of its daughter nuclei present in the spectrum. Special attention was given to the study of the parasitic neutrons present in the experimental area, produced via charged particle reactions induced by the particle beam and from neutron scattering. Details on the data analysis and results are presented.", + "source": "submitter" + } + ], + "copyright": [ + { + "year": 2021, + "holder": "The Author(s)" + } + ], + "document_type": [ + "article" + ], + "control_number": 1938038, + "number_of_pages": 10, + "inspire_categories": [ + { + "term": "Experiment-HEP" + } + ], + "accelerator_experiments": [ + { + "record": { + "$ref": "https://inspirehep.net/api/experiments/1778060" + }, + "legacy_name": "FLUKA" + } + ], + "external_system_identifiers": [ + { + "value": "2783291", + "schema": "CDS" + }, + { + "value": "2021MI21", + "schema": "NSR" + } + ] + }, + "id": "1938038", + "created": "2021-10-04T13:26:28.908220+00:00", + "uuid": "ced00c67-9173-4298-b8d2-daee1430eab1" + } + ] + } +} \ No newline at end of file diff --git a/site/tests/inspire_harvester/data/record_with_no_cds_DOI_multiple_doc_type.json b/site/tests/inspire_harvester/data/record_with_no_cds_DOI_multiple_doc_type.json index dd673297..dc7156b6 100644 --- a/site/tests/inspire_harvester/data/record_with_no_cds_DOI_multiple_doc_type.json +++ b/site/tests/inspire_harvester/data/record_with_no_cds_DOI_multiple_doc_type.json @@ -32,8 +32,8 @@ ], "documents": [ { - "key": "26aec07fc9c6bea56cde13f86d74d96b", - "url": "https://inspirehep.net/files/26aec07fc9c6bea56cde13f86d74d96b", + "key": "4550b6ee36afc3fdedc08d0423375ab4", + "url": "https://inspirehep.net/files/4550b6ee36afc3fdedc08d0423375ab4", "filename": "PhysRevD.104.L111102.pdf", "fulltext": true, "material": "publication" diff --git a/site/tests/inspire_harvester/data/record_with_no_cds_DOI_multiple_doc_type2.json b/site/tests/inspire_harvester/data/record_with_no_cds_DOI_multiple_doc_type2.json new file mode 100644 index 00000000..ffaa8503 --- /dev/null +++ b/site/tests/inspire_harvester/data/record_with_no_cds_DOI_multiple_doc_type2.json @@ -0,0 +1,2422 @@ +{ + "hits": { + "total": 1, + "hits": [ + { + "id": "1992535", + "links": { + "bibtex": "https://inspirehep.net/api/literature/1992535?format=bibtex", + "latex-eu": "https://inspirehep.net/api/literature/1992535?format=latex-eu", + "latex-us": "https://inspirehep.net/api/literature/1992535?format=latex-us", + "json": "https://inspirehep.net/api/literature/1992535?format=json", + "json-expanded": "https://inspirehep.net/api/literature/1992535?format=json-expanded", + "cv": "https://inspirehep.net/api/literature/1992535?format=cv", + "citations": "https://inspirehep.net/api/literature/?q=refersto%3Arecid%3A1992535" + }, + "revision_id": 29, + "updated": "2025-06-05T07:26:20.266932+00:00", + "metadata": { + "citation_count": 36, + "publication_info": [ + { + "year": 2021, + "artid": "L111102", + "material": "publication", + "journal_issue": "11", + "journal_title": "Phys.Rev.D", + "journal_record": { + "$ref": "https://inspirehep.net/api/journals/1613970" + }, + "journal_volume": "104" + } + ], + "documents": [ + { + "key": "4550b6ee36afc3fdedc08d0423375ab4", + "url": "https://inspirehep.net/files/4550b6ee36afc3fdedc08d0423375ab4", + "filename": "PhysRevD.104.L111102.pdf", + "fulltext": true, + "material": "publication" + } + ], + "citation_count_without_self_citations": 28, + "core": true, + "dois": [ + { + "value": "10.1103/PhysRevD.104.L111102", + "source": "APS", + "material": "publication" + }, + { + "value": "10.1103/PhysRevD.104.L111102", + "source": "arXiv", + "material": "publication" + } + ], + "titles": [ + { + "title": "Search for pseudoscalar bosons decaying into $e^+e^-$ pairs in the NA64 experiment at the CERN SPS", + "source": "APS" + }, + { + "title": "Search for pseudoscalar bosons decaying into $e^+e^-$ pairs in the NA64 experiment at the CERN SPS", + "source": "arXiv" + }, + { + "title": "Search for pseudoscalar bosons decaying into $e^+e^-$ pairs", + "source": "arXiv" + } + ], + "$schema": "https://inspirehep.net/schemas/records/hep.json", + "authors": [ + { + "uuid": "30c36b14-c7d9-44b9-a7cc-d005c741043d", + "record": { + "$ref": "https://inspirehep.net/api/authors/1992536" + }, + "full_name": "Andreev, Yu.M.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Moscow, INR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903030" + } + } + ], + "signature_block": "ANDRAFy", + "raw_affiliations": [ + { + "value": "Institute for Nuclear Research, 117312 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Institute for Nuclear Research,117312 Moscow,Russia" + } + ] + }, + { + "uuid": "56f80aad-7c93-464c-ab7a-e6c74d45c4bf", + "record": { + "$ref": "https://inspirehep.net/api/authors/1034884" + }, + "full_name": "Banerjee, D.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "CERN", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902725" + } + } + ], + "signature_block": "BANARJYd", + "raw_affiliations": [ + { + "value": "CERN, European Organization for Nuclear Research, CH-1211 Geneva, Switzerland", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "CERN,European Organization for Nuclear Research,CH-1211 Geneva,Switzerland" + } + ] + }, + { + "uuid": "e93c74fa-e51c-42ea-a064-b4ee350fc991", + "record": { + "$ref": "https://inspirehep.net/api/authors/1070564" + }, + "full_name": "Bernhard, J.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "CERN", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902725" + } + } + ], + "signature_block": "BARNADj", + "curated_relation": true, + "raw_affiliations": [ + { + "value": "CERN, European Organization for Nuclear Research, CH-1211 Geneva, Switzerland", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "CERN,European Organization for Nuclear Research,CH-1211 Geneva,Switzerland" + } + ] + }, + { + "uuid": "331a0b13-0d52-4ee8-893d-2787357c730b", + "record": { + "$ref": "https://inspirehep.net/api/authors/1876562" + }, + "full_name": "Burtsev, V.E.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Dubna, JINR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902780" + } + } + ], + "signature_block": "BARTSAFv", + "raw_affiliations": [ + { + "value": "Joint Institute for Nuclear Research, 141980 Dubna, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Joint Institute for Nuclear Research,141980 Dubna,Russia" + } + ] + }, + { + "uuid": "99433175-63d7-471d-86f2-7f789db294a6", + "record": { + "$ref": "https://inspirehep.net/api/authors/1599780" + }, + "full_name": "Charitonidis, N.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "CERN", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902725" + } + } + ], + "signature_block": "CARATANADn", + "raw_affiliations": [ + { + "value": "CERN, European Organization for Nuclear Research, CH-1211 Geneva, Switzerland", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "CERN,European Organization for Nuclear Research,CH-1211 Geneva,Switzerland" + } + ] + }, + { + "uuid": "1cadff79-e1e2-4f88-8bcc-0443a335481a", + "record": { + "$ref": "https://inspirehep.net/api/authors/1876563" + }, + "full_name": "Chumakov, A.G.", + "affiliations": [ + { + "value": "Tomsk Pedagogical Inst.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903659" + } + }, + { + "value": "Tomsk Polytechnic U.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903660" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + } + ], + "signature_block": "CANACAVa", + "raw_affiliations": [ + { + "value": "Tomsk State Pedagogical University, 634061 Tomsk, Russia", + "source": "APS" + }, + { + "value": "Tomsk Polytechnic University, 634050 Tomsk, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Tomsk Polytechnic University,634050 Tomsk,Russia" + }, + { + "value": "Tomsk State Pedagogical University,634061 Tomsk,Russia" + } + ] + }, + { + "uuid": "22c878ba-f0ec-4b56-8071-f18143c8fa14", + "record": { + "$ref": "https://inspirehep.net/api/authors/1910368" + }, + "full_name": "Cooke, D.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "University Coll. London", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903311" + } + } + ], + "signature_block": "CACd", + "raw_affiliations": [ + { + "value": "UCL Departement of Physics and Astronomy, University College London, Gower St., London WC1E 6BT, United Kingdom", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "UCL Departement of Physics and Astronomy,University College London,Gower St. London WC1E 6BT,United Kingdom" + } + ] + }, + { + "uuid": "203effd7-1599-4101-997d-1e055be92e48", + "record": { + "$ref": "https://inspirehep.net/api/authors/1045321" + }, + "full_name": "Crivelli, P.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Zurich, ETH", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903369" + } + } + ], + "signature_block": "CRAVALp", + "raw_affiliations": [ + { + "value": "ETH Zürich, Institute for Particle Physics and Astrophysics, CH-8093 Zürich, Switzerland", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "ETH Zürich,Institute for Particle Physics and Astrophysics,CH-8093 Zürich,Switzerland" + } + ] + }, + { + "uuid": "6341f628-81c3-44d3-8479-c7b06b0f35d4", + "record": { + "$ref": "https://inspirehep.net/api/authors/1876560" + }, + "full_name": "Depero, E.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Zurich, ETH", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903369" + } + } + ], + "signature_block": "DAPARe", + "raw_affiliations": [ + { + "value": "ETH Zürich, Institute for Particle Physics and Astrophysics, CH-8093 Zürich, Switzerland", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "ETH Zürich,Institute for Particle Physics and Astrophysics,CH-8093 Zürich,Switzerland" + } + ] + }, + { + "uuid": "e3cc2400-412b-49da-8c0e-1679658413fd", + "record": { + "$ref": "https://inspirehep.net/api/authors/1876565" + }, + "full_name": "Dermenev, A.V.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Moscow, INR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903030" + } + } + ], + "signature_block": "DARNANAFa", + "raw_affiliations": [ + { + "value": "Institute for Nuclear Research, 117312 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Institute for Nuclear Research,117312 Moscow,Russia" + } + ] + }, + { + "uuid": "0fb4f1a6-a102-4e0b-83bd-8ba80e031135", + "record": { + "$ref": "https://inspirehep.net/api/authors/1011541" + }, + "full_name": "Donskov, S.V.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Serpukhov, IHEP", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903194" + } + } + ], + "signature_block": "DANSCAVs", + "raw_affiliations": [ + { + "value": "State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center ’Kurchatov Institute’ (IHEP), 142281 Protvino, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center 'Kurchatov Institute' (IHEP),142281 Protvino,Russia" + } + ] + }, + { + "uuid": "0b1273b5-f1cb-4eb1-b3fa-165551d07362", + "record": { + "$ref": "https://inspirehep.net/api/authors/1391711" + }, + "full_name": "Dusaev, R.R.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Tomsk Polytechnic U.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903660" + } + } + ], + "signature_block": "DASAFr", + "raw_affiliations": [ + { + "value": "Tomsk Polytechnic University, 634050 Tomsk, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Tomsk Polytechnic University,634050 Tomsk,Russia" + } + ] + }, + { + "uuid": "9e4ed6b8-00b8-49fd-936c-0a5a5ad011ae", + "record": { + "$ref": "https://inspirehep.net/api/authors/1607170" + }, + "full_name": "Enik, T.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Dubna, JINR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902780" + } + } + ], + "signature_block": "ENACt", + "curated_relation": true, + "raw_affiliations": [ + { + "value": "Joint Institute for Nuclear Research, 141980 Dubna, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Joint Institute for Nuclear Research,141980 Dubna,Russia" + } + ] + }, + { + "uuid": "f74dec1d-e8d8-423c-852a-ede4ded435d4", + "record": { + "$ref": "https://inspirehep.net/api/authors/1992538" + }, + "full_name": "Feshchenko, A.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Dubna, JINR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902780" + } + } + ], + "signature_block": "FASCANCa", + "raw_affiliations": [ + { + "value": "Joint Institute for Nuclear Research, 141980 Dubna, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Joint Institute for Nuclear Research,141980 Dubna,Russia" + } + ] + }, + { + "uuid": "37bfa584-6c55-47d6-a3ec-93213f299322", + "record": { + "$ref": "https://inspirehep.net/api/authors/1009360" + }, + "full_name": "Frolov, V.N.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Dubna, JINR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902780" + } + } + ], + "signature_block": "FRALAVv", + "raw_affiliations": [ + { + "value": "Joint Institute for Nuclear Research, 141980 Dubna, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Joint Institute for Nuclear Research,141980 Dubna,Russia" + } + ] + }, + { + "uuid": "7a8065a4-8b2d-46d0-9099-98eccb27e6d5", + "record": { + "$ref": "https://inspirehep.net/api/authors/1283538" + }, + "full_name": "Gardikiotis, A.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Patras U.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903743" + } + } + ], + "signature_block": "GARDACATa", + "raw_affiliations": [ + { + "value": "Physics Department, University of Patras, 265 04 Patras, Greece", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Physics Department,University of Patras,265 04 Patras,Greece" + } + ] + }, + { + "uuid": "4554f0da-1233-4758-97af-c3204ec6210a", + "record": { + "$ref": "https://inspirehep.net/api/authors/1008577" + }, + "full_name": "Gerassimov, S.G.", + "affiliations": [ + { + "value": "TUM-IAS, Munich", + "record": { + "$ref": "https://inspirehep.net/api/institutions/911544" + } + }, + { + "value": "Lebedev Inst.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902955" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Munich, Tech. U.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903037" + } + }, + { + "value": "LPI, Moscow (main)", + "record": { + "$ref": "https://inspirehep.net/api/institutions/1256696" + } + } + ], + "signature_block": "GARASANAVs", + "raw_affiliations": [ + { + "value": "Technische Universität München, Physik Department, 85748 Garching, Germany", + "source": "APS" + }, + { + "value": "P.N. Lebedev Physical Institute of the Russian Academy of Sciences, 119 991 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Technische Universität München,Physik Department,85748 Garching,Germany" + }, + { + "value": "P.N. Lebedev Physical Institute,Moscow,Russia,119 991 Moscow,Russia" + } + ] + }, + { + "uuid": "00e5f85a-8aa0-45ef-9487-8bf617e55089", + "record": { + "$ref": "https://inspirehep.net/api/authors/1008194" + }, + "full_name": "Gninenko, S.N.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Moscow, INR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903030" + } + } + ], + "signature_block": "GNANANCs", + "raw_affiliations": [ + { + "value": "Institute for Nuclear Research, 117312 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Institute for Nuclear Research,117312 Moscow,Russia" + } + ] + }, + { + "uuid": "1e297ef8-0117-4f7d-83a2-d27add76e4e6", + "record": { + "$ref": "https://inspirehep.net/api/authors/1876567" + }, + "full_name": "Hösgen, M.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Bonn U., HISKP", + "record": { + "$ref": "https://inspirehep.net/api/institutions/908572" + } + } + ], + "signature_block": "HASGANm", + "raw_affiliations": [ + { + "value": "Universität Bonn, Helmholtz-Institut für Strahlen-und Kernphysik, 53115 Bonn, Germany", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Universität Bonn,Helmholtz-Institut für Strahlen-und Kernphysik,53115 Bonn,Germany" + } + ] + }, + { + "uuid": "e9b6b061-30d1-4fd9-b5c0-4981f7482b20", + "record": { + "$ref": "https://inspirehep.net/api/authors/1639537" + }, + "full_name": "Jeckel, M.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "CERN", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902725" + } + } + ], + "signature_block": "JACALm", + "raw_affiliations": [ + { + "value": "CERN, European Organization for Nuclear Research, CH-1211 Geneva, Switzerland", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "CERN,European Organization for Nuclear Research,CH-1211 Geneva,Switzerland" + } + ] + }, + { + "uuid": "177ed12a-e59b-4f43-b456-31b20d34f7db", + "record": { + "$ref": "https://inspirehep.net/api/authors/1050796" + }, + "full_name": "Kachanov, V.A.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Serpukhov, IHEP", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903194" + } + } + ], + "signature_block": "CACANAVv", + "raw_affiliations": [ + { + "value": "State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center ’Kurchatov Institute’ (IHEP), 142281 Protvino, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center 'Kurchatov Institute' (IHEP),142281 Protvino,Russia" + } + ] + }, + { + "uuid": "1a007fb2-f57c-45ed-b618-41323d58c0e8", + "record": { + "$ref": "https://inspirehep.net/api/authors/1876568" + }, + "full_name": "Karneyeu, A.E.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Moscow, INR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903030" + } + } + ], + "signature_block": "CARNYa", + "raw_affiliations": [ + { + "value": "Institute for Nuclear Research, 117312 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Institute for Nuclear Research,117312 Moscow,Russia" + } + ] + }, + { + "uuid": "73f27656-2265-4b0a-9686-a064f4d8a6e1", + "record": { + "$ref": "https://inspirehep.net/api/authors/1062433" + }, + "full_name": "Kekelidze, G.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Dubna, JINR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902780" + } + } + ], + "signature_block": "CACALADSg", + "raw_affiliations": [ + { + "value": "Joint Institute for Nuclear Research, 141980 Dubna, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Joint Institute for Nuclear Research,141980 Dubna,Russia" + } + ] + }, + { + "uuid": "47bfbbcb-61a4-4eec-9e5e-812db0057706", + "record": { + "$ref": "https://inspirehep.net/api/authors/1003144" + }, + "full_name": "Ketzer, B.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Bonn U., HISKP", + "record": { + "$ref": "https://inspirehep.net/api/institutions/908572" + } + } + ], + "signature_block": "CATSARb", + "raw_affiliations": [ + { + "value": "Universität Bonn, Helmholtz-Institut für Strahlen-und Kernphysik, 53115 Bonn, Germany", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Universität Bonn,Helmholtz-Institut für Strahlen-und Kernphysik,53115 Bonn,Germany" + } + ] + }, + { + "uuid": "8621869e-bfa4-4ae8-9786-ffa8e52c0ba0", + "record": { + "$ref": "https://inspirehep.net/api/authors/1074204" + }, + "full_name": "Kirpichnikov, D.V.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Moscow, INR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903030" + } + } + ], + "signature_block": "CARPACHNACAVd", + "raw_affiliations": [ + { + "value": "Institute for Nuclear Research, 117312 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Institute for Nuclear Research,117312 Moscow,Russia" + } + ] + }, + { + "uuid": "e36b4781-4ed0-4c0c-afdb-c33b8ac547cf", + "record": { + "$ref": "https://inspirehep.net/api/authors/1002797" + }, + "full_name": "Kirsanov, M.M.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Moscow, INR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903030" + } + } + ], + "signature_block": "CARSANAVm", + "raw_affiliations": [ + { + "value": "Institute for Nuclear Research, 117312 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Institute for Nuclear Research,117312 Moscow,Russia" + } + ] + }, + { + "uuid": "427de10c-cbbd-444a-95cc-cc8378bcdd66", + "record": { + "$ref": "https://inspirehep.net/api/authors/1907803" + }, + "full_name": "Kolosov, V.N.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Serpukhov, IHEP", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903194" + } + } + ], + "signature_block": "CALASAVv", + "raw_affiliations": [ + { + "value": "State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center ’Kurchatov Institute’ (IHEP), 142281 Protvino, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center 'Kurchatov Institute' (IHEP),142281 Protvino,Russia" + } + ] + }, + { + "uuid": "8b0e9272-9831-4ed0-b94e-f74ba620817c", + "record": { + "$ref": "https://inspirehep.net/api/authors/1002227" + }, + "full_name": "Konorov, I.V.", + "affiliations": [ + { + "value": "TUM-IAS, Munich", + "record": { + "$ref": "https://inspirehep.net/api/institutions/911544" + } + }, + { + "value": "Lebedev Inst.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902955" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Munich, Tech. U.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903037" + } + }, + { + "value": "LPI, Moscow (main)", + "record": { + "$ref": "https://inspirehep.net/api/institutions/1256696" + } + } + ], + "signature_block": "CANARAVi", + "raw_affiliations": [ + { + "value": "Technische Universität München, Physik Department, 85748 Garching, Germany", + "source": "APS" + }, + { + "value": "P.N. Lebedev Physical Institute of the Russian Academy of Sciences, 119 991 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Technische Universität München,Physik Department,85748 Garching,Germany" + }, + { + "value": "P.N. Lebedev Physical Institute,Moscow,Russia,119 991 Moscow,Russia" + } + ] + }, + { + "uuid": "2cffe3e3-8b61-41f1-89b5-b3faa7002053", + "record": { + "$ref": "https://inspirehep.net/api/authors/1001998" + }, + "full_name": "Kovalenko, S.G.", + "affiliations": [ + { + "value": "Chile U., Santiago", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902731" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Andres Bello Natl. U.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907969" + } + } + ], + "signature_block": "CAVALANCs", + "raw_affiliations": [ + { + "value": "Millennium Institute for Subatomic Physics at the High-Energy Frontier (SAPHIR) of ANID, Fernández Concha 700, Santiago, Chile", + "source": "APS" + }, + { + "value": "Departamento de Ciencias Físicas, Universidad Andres Bello, Sazié 2212, Piso 7, Santiago, Chile", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Departamento de Ciencias Físicas,Universidad Andres Bello,Sazié2212,Piso 7,Santiago,Chile" + } + ] + }, + { + "uuid": "ff26c423-6958-4612-a3a9-a0c38cc09d79", + "record": { + "$ref": "https://inspirehep.net/api/authors/1001923" + }, + "full_name": "Kramarenko, V.A.", + "affiliations": [ + { + "value": "SINP, Moscow", + "record": { + "$ref": "https://inspirehep.net/api/institutions/908795" + } + }, + { + "value": "Dubna, JINR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902780" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + } + ], + "signature_block": "CRANARANCv", + "raw_affiliations": [ + { + "value": "Skobeltsyn Institute of Nuclear Physics, Lomonosov Moscow State University, 119991 Moscow, Russia", + "source": "APS" + }, + { + "value": "Joint Institute for Nuclear Research, 141980 Dubna, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Joint Institute for Nuclear Research,141980 Dubna,Russia" + }, + { + "value": "Skobeltsyn Institute of Nuclear Physics,Lomonosov Moscow State University,119991 Moscow,Russia" + } + ] + }, + { + "uuid": "f84024e3-1b0f-4fd7-a703-e8f3a58e3aaa", + "record": { + "$ref": "https://inspirehep.net/api/authors/1070742" + }, + "full_name": "Kravchuk, L.V.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Moscow, INR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903030" + } + } + ], + "signature_block": "CRAVCACl", + "raw_affiliations": [ + { + "value": "Institute for Nuclear Research, 117312 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Institute for Nuclear Research,117312 Moscow,Russia" + } + ] + }, + { + "uuid": "06850883-3395-4644-b2b6-bdc2927afd34", + "record": { + "$ref": "https://inspirehep.net/api/authors/1001900" + }, + "full_name": "Krasnikov, N.V.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Dubna, JINR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902780" + } + }, + { + "value": "Moscow, INR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903030" + } + } + ], + "signature_block": "CRASNACAVn", + "raw_affiliations": [ + { + "value": "Joint Institute for Nuclear Research, 141980 Dubna, Russia", + "source": "APS" + }, + { + "value": "Institute for Nuclear Research, 117312 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Joint Institute for Nuclear Research,141980 Dubna,Russia" + }, + { + "value": "Institute for Nuclear Research,117312 Moscow,Russia" + } + ] + }, + { + "uuid": "19eb1116-242c-498a-bdb5-704535e1f396", + "record": { + "$ref": "https://inspirehep.net/api/authors/1001595" + }, + "full_name": "Kuleshov, S.V.", + "affiliations": [ + { + "value": "Chile U., Santiago", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902731" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Andres Bello Natl. U.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907969" + } + } + ], + "signature_block": "CALASAVs", + "raw_affiliations": [ + { + "value": "Millennium Institute for Subatomic Physics at the High-Energy Frontier (SAPHIR) of ANID, Fernández Concha 700, Santiago, Chile", + "source": "APS" + }, + { + "value": "Departamento de Ciencias Físicas, Universidad Andres Bello, Sazié 2212, Piso 7, Santiago, Chile", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Departamento de Ciencias Físicas,Universidad Andres Bello,Sazié2212,Piso 7,Santiago,Chile" + } + ] + }, + { + "uuid": "ae331996-d6fc-49d7-a727-8705a87632af", + "record": { + "$ref": "https://inspirehep.net/api/authors/999453" + }, + "full_name": "Lyubovitskij, V.E.", + "affiliations": [ + { + "value": "Santa Maria U., Valparaiso", + "record": { + "$ref": "https://inspirehep.net/api/institutions/904589" + } + }, + { + "value": "Chile U., Santiago", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902731" + } + }, + { + "value": "Tomsk Pedagogical Inst.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903659" + } + }, + { + "value": "Tomsk Polytechnic U.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903660" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "CCTVal, Valparaiso", + "record": { + "$ref": "https://inspirehep.net/api/institutions/911929" + } + } + ], + "signature_block": "LABAVATSCAJv", + "curated_relation": true, + "raw_affiliations": [ + { + "value": "Universidad Técnica Federico Santa María, Centro Científica y Tecnológica de Valparaíso-CCTVal, 2390123 Valparaíso, Chile", + "source": "APS" + }, + { + "value": "Millennium Institute for Subatomic Physics at the High-Energy Frontier (SAPHIR) of ANID, Fernández Concha 700, Santiago, Chile", + "source": "APS" + }, + { + "value": "Tomsk State Pedagogical University, 634061 Tomsk, Russia", + "source": "APS" + }, + { + "value": "Tomsk Polytechnic University, 634050 Tomsk, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Tomsk Polytechnic University,634050 Tomsk,Russia" + }, + { + "value": "Tomsk State Pedagogical University,634061 Tomsk,Russia" + }, + { + "value": "Universidad Técnica Federico Santa María,2390123 Valparaíso,Chile" + } + ] + }, + { + "uuid": "cf10fe18-f156-4f61-a91e-d2b02868b44e", + "record": { + "$ref": "https://inspirehep.net/api/authors/1992539" + }, + "full_name": "Lysan, V.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Dubna, JINR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902780" + } + } + ], + "signature_block": "LASANv", + "raw_affiliations": [ + { + "value": "Joint Institute for Nuclear Research, 141980 Dubna, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Joint Institute for Nuclear Research,141980 Dubna,Russia" + } + ] + }, + { + "uuid": "2532affc-4ab4-4c61-a87d-ffe3ff1ff260", + "record": { + "$ref": "https://inspirehep.net/api/authors/998249" + }, + "full_name": "Matveev, V.A.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Dubna, JINR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902780" + } + } + ], + "signature_block": "MATVAFv", + "raw_affiliations": [ + { + "value": "Joint Institute for Nuclear Research, 141980 Dubna, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Joint Institute for Nuclear Research,141980 Dubna,Russia" + } + ] + }, + { + "uuid": "66107d8e-1a77-48a3-81cb-65abc12a2419", + "record": { + "$ref": "https://inspirehep.net/api/authors/1043529" + }, + "full_name": "Mikhailov, Yu.V.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Serpukhov, IHEP", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903194" + } + } + ], + "signature_block": "MACALAVy", + "raw_affiliations": [ + { + "value": "State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center ’Kurchatov Institute’ (IHEP), 142281 Protvino, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center 'Kurchatov Institute' (IHEP),142281 Protvino,Russia" + } + ] + }, + { + "uuid": "b5e018b8-a003-4c25-b002-721b9064ec1e", + "record": { + "$ref": "https://inspirehep.net/api/authors/1121788" + }, + "full_name": "Molina Bueno, L.", + "affiliations": [ + { + "value": "Zurich, ETH", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903369" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + } + ], + "signature_block": "BANl", + "raw_affiliations": [ + { + "value": "ETH Zürich, Institute for Particle Physics and Astrophysics, CH-8093 Zürich, Switzerland", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV), Carrer del Catedrtic Jos Beltrn Martinez, 2, 46980 Paterna, Valencia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "ETH Zürich,Institute for Particle Physics and Astrophysics,CH-8093 Zürich,Switzerland" + } + ] + }, + { + "uuid": "5c4fac53-ebc8-4e7f-990d-08faac1640ca", + "record": { + "$ref": "https://inspirehep.net/api/authors/1992540" + }, + "full_name": "Peshekhonov, D.V.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Dubna, JINR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902780" + } + } + ], + "signature_block": "PASACANAVd", + "raw_affiliations": [ + { + "value": "Joint Institute for Nuclear Research, 141980 Dubna, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Joint Institute for Nuclear Research,141980 Dubna,Russia" + } + ] + }, + { + "uuid": "a24812ae-eb43-48b0-90c1-91d07dbc4977", + "record": { + "$ref": "https://inspirehep.net/api/authors/1898990" + }, + "full_name": "Polyakov, V.A.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Serpukhov, IHEP", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903194" + } + } + ], + "signature_block": "PALACAVv", + "raw_affiliations": [ + { + "value": "State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center ’Kurchatov Institute’ (IHEP), 142281 Protvino, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center 'Kurchatov Institute' (IHEP),142281 Protvino,Russia" + } + ] + }, + { + "uuid": "85040e98-9eb0-4eb7-adf2-4f9224a05690", + "record": { + "$ref": "https://inspirehep.net/api/authors/1026910" + }, + "full_name": "Radics, B.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Zurich, ETH", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903369" + } + } + ], + "signature_block": "RADACb", + "raw_affiliations": [ + { + "value": "ETH Zürich, Institute for Particle Physics and Astrophysics, CH-8093 Zürich, Switzerland", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "ETH Zürich,Institute for Particle Physics and Astrophysics,CH-8093 Zürich,Switzerland" + } + ] + }, + { + "uuid": "bf9cb194-0a3d-4686-a0bb-94442c9ddf08", + "record": { + "$ref": "https://inspirehep.net/api/authors/2128027" + }, + "full_name": "Rojas, R.", + "affiliations": [ + { + "value": "Santa Maria U., Valparaiso", + "record": { + "$ref": "https://inspirehep.net/api/institutions/904589" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "CCTVal, Valparaiso", + "record": { + "$ref": "https://inspirehep.net/api/institutions/911929" + } + } + ], + "signature_block": "RAJr", + "raw_affiliations": [ + { + "value": "Universidad Técnica Federico Santa María, Centro Científica y Tecnológica de Valparaíso-CCTVal, 2390123 Valparaíso, Chile", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Universidad Técnica Federico Santa María,2390123 Valparaíso,Chile" + } + ] + }, + { + "uuid": "6b2426a2-32af-4f8c-ad88-fd5601845e5b", + "record": { + "$ref": "https://inspirehep.net/api/authors/991041" + }, + "full_name": "Rubbia, A.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Zurich, ETH", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903369" + } + } + ], + "signature_block": "RABa", + "raw_affiliations": [ + { + "value": "ETH Zürich, Institute for Particle Physics and Astrophysics, CH-8093 Zürich, Switzerland", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "ETH Zürich,Institute for Particle Physics and Astrophysics,CH-8093 Zürich,Switzerland" + } + ] + }, + { + "uuid": "bb8980b3-1240-486d-885d-ee6a7d390874", + "record": { + "$ref": "https://inspirehep.net/api/authors/990489" + }, + "full_name": "Samoylenko, V.D.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Serpukhov, IHEP", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903194" + } + } + ], + "signature_block": "SANYLANCv", + "raw_affiliations": [ + { + "value": "State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center ’Kurchatov Institute’ (IHEP), 142281 Protvino, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "State Scientific Center of the Russian Federation Institute for High Energy Physics of National Research Center 'Kurchatov Institute' (IHEP),142281 Protvino,Russia" + } + ] + }, + { + "uuid": "be9589ea-a862-4567-b9fd-08a8cbcce30e", + "record": { + "$ref": "https://inspirehep.net/api/authors/1876572" + }, + "full_name": "Sieber, H.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Zurich, ETH", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903369" + } + } + ], + "signature_block": "SABARh", + "raw_affiliations": [ + { + "value": "ETH Zürich, Institute for Particle Physics and Astrophysics, CH-8093 Zürich, Switzerland", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "ETH Zürich,Institute for Particle Physics and Astrophysics,CH-8093 Zürich,Switzerland" + } + ] + }, + { + "uuid": "bee6965d-9798-4c14-a470-21d1a2957fcb", + "record": { + "$ref": "https://inspirehep.net/api/authors/1074204" + }, + "full_name": "Shchukin, D.", + "affiliations": [ + { + "value": "Lebedev Inst.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902955" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "LPI, Moscow (main)", + "record": { + "$ref": "https://inspirehep.net/api/institutions/1256696" + } + } + ], + "signature_block": "SCACANd", + "raw_affiliations": [ + { + "value": "P.N. Lebedev Physical Institute of the Russian Academy of Sciences, 119 991 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "P.N. Lebedev Physical Institute,Moscow,Russia,119 991 Moscow,Russia" + } + ] + }, + { + "uuid": "a5721dad-1661-4b21-8e37-f86ff884ba07", + "record": { + "$ref": "https://inspirehep.net/api/authors/1062381" + }, + "full_name": "Tikhomirov, V.O.", + "affiliations": [ + { + "value": "Lebedev Inst.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902955" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "LPI, Moscow (main)", + "record": { + "$ref": "https://inspirehep.net/api/institutions/1256696" + } + } + ], + "signature_block": "TACANARAVv", + "curated_relation": true, + "raw_affiliations": [ + { + "value": "P.N. Lebedev Physical Institute of the Russian Academy of Sciences, 119 991 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "P.N. Lebedev Physical Institute,Moscow,Russia,119 991 Moscow,Russia" + } + ] + }, + { + "uuid": "ef1ef9e0-9627-4660-9764-b5e6a582ed37", + "record": { + "$ref": "https://inspirehep.net/api/authors/1876573" + }, + "full_name": "Tlisova, I.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Moscow, INR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903030" + } + } + ], + "signature_block": "TLASAVi", + "raw_affiliations": [ + { + "value": "Institute for Nuclear Research, 117312 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Institute for Nuclear Research,117312 Moscow,Russia" + } + ] + }, + { + "uuid": "ab8b1910-b100-4cca-a0f9-d892f29dad6d", + "record": { + "$ref": "https://inspirehep.net/api/authors/1039258" + }, + "full_name": "Toropin, A.N.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Moscow, INR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903030" + } + } + ], + "signature_block": "TARAPANa", + "raw_affiliations": [ + { + "value": "Institute for Nuclear Research, 117312 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Institute for Nuclear Research,117312 Moscow,Russia" + } + ] + }, + { + "uuid": "d795e71b-24cc-4002-9f3c-ebc4abe1be03", + "record": { + "$ref": "https://inspirehep.net/api/authors/1992552" + }, + "full_name": "Trifonov, A.Yu.", + "affiliations": [ + { + "value": "Tomsk Pedagogical Inst.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903659" + } + }, + { + "value": "Tomsk Polytechnic U.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903660" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + } + ], + "signature_block": "TRAFANAVa", + "raw_affiliations": [ + { + "value": "Tomsk State Pedagogical University, 634061 Tomsk, Russia", + "source": "APS" + }, + { + "value": "Tomsk Polytechnic University, 634050 Tomsk, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Tomsk Polytechnic University,634050 Tomsk,Russia" + }, + { + "value": "Tomsk State Pedagogical University,634061 Tomsk,Russia" + } + ] + }, + { + "uuid": "5b193a05-0a8c-4b43-b35e-1032f5280763", + "record": { + "$ref": "https://inspirehep.net/api/authors/1876575" + }, + "full_name": "Vasilishin, B.I.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Tomsk Polytechnic U.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/903660" + } + } + ], + "signature_block": "VASALASANb", + "raw_affiliations": [ + { + "value": "Tomsk Polytechnic University, 634050 Tomsk, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Tomsk Polytechnic University,634050 Tomsk,Russia" + } + ] + }, + { + "uuid": "6ef1a9c5-63bb-4d69-9392-2cc856b603ff", + "record": { + "$ref": "https://inspirehep.net/api/authors/1910374" + }, + "full_name": "Vasquez Arenas, G.", + "affiliations": [ + { + "value": "Santa Maria U., Valparaiso", + "record": { + "$ref": "https://inspirehep.net/api/institutions/904589" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "CCTVal, Valparaiso", + "record": { + "$ref": "https://inspirehep.net/api/institutions/911929" + } + } + ], + "signature_block": "ARANg", + "raw_affiliations": [ + { + "value": "Universidad Técnica Federico Santa María, Centro Científica y Tecnológica de Valparaíso-CCTVal, 2390123 Valparaíso, Chile", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Universidad Técnica Federico Santa María,2390123 Valparaíso,Chile" + } + ] + }, + { + "uuid": "e53204e3-6e01-4090-8d0b-28c6e4631fce", + "record": { + "$ref": "https://inspirehep.net/api/authors/1876577" + }, + "full_name": "Volkov, P.V.", + "affiliations": [ + { + "value": "SINP, Moscow", + "record": { + "$ref": "https://inspirehep.net/api/institutions/908795" + } + }, + { + "value": "Dubna, JINR", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902780" + } + }, + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + } + ], + "signature_block": "VALCAVp", + "raw_affiliations": [ + { + "value": "Skobeltsyn Institute of Nuclear Physics, Lomonosov Moscow State University, 119991 Moscow, Russia", + "source": "APS" + }, + { + "value": "Joint Institute for Nuclear Research, 141980 Dubna, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Joint Institute for Nuclear Research,141980 Dubna,Russia" + }, + { + "value": "Skobeltsyn Institute of Nuclear Physics,Lomonosov Moscow State University,119991 Moscow,Russia" + } + ] + }, + { + "uuid": "8b274e78-4aad-401b-bcfb-be652b05b914", + "record": { + "$ref": "https://inspirehep.net/api/authors/1992553" + }, + "full_name": "Volkov, V.Yu.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "SINP, Moscow", + "record": { + "$ref": "https://inspirehep.net/api/institutions/908795" + } + } + ], + "signature_block": "VALCAVv", + "raw_affiliations": [ + { + "value": "Skobeltsyn Institute of Nuclear Physics, Lomonosov Moscow State University, 119991 Moscow, Russia", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Skobeltsyn Institute of Nuclear Physics,Lomonosov Moscow State University,119991 Moscow,Russia" + } + ] + }, + { + "uuid": "d2841447-e76f-485a-8a67-20c5f7636dc0", + "record": { + "$ref": "https://inspirehep.net/api/authors/1478928" + }, + "full_name": "Ulloa, P.", + "affiliations": [ + { + "value": "Valencia U., IFIC", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907907" + } + }, + { + "value": "Andres Bello Natl. U.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/907969" + } + } + ], + "signature_block": "ULp", + "curated_relation": true, + "raw_affiliations": [ + { + "value": "Departamento de Ciencias Físicas, Universidad Andres Bello, Sazié 2212, Piso 7, Santiago, Chile", + "source": "APS" + }, + { + "value": "Instituto de Fisica Corpuscular (CSIC/UV),Carrer del Catedrátic José Beltrán Martinez,2,46980 Paterna,Valencia" + }, + { + "value": "Departamento de Ciencias Físicas,Universidad Andres Bello,Sazié2212,Piso 7,Santiago,Chile" + } + ] + } + ], + "curated": true, + "figures": [ + { + "key": "1fa3b8447ad01f97a1956271a2479331", + "url": "https://inspirehep.net/files/1fa3b8447ad01f97a1956271a2479331", + "label": "fig:setup", + "source": "arxiv", + "caption": "The NA64 setup to search for $A'(a)\\to \\ee$ decays of the bremsstrahlung $A'(a)$ produced in the reaction $eZ \\to eZA'(a) $ of the 150 GeV electrons incident on the active WCAL target. The figure is reproduced from Ref. \\cite{visible-2018-analysis}.", + "filename": "setup_2018_vis.png", + "material": "preprint" + }, + { + "key": "ca453d4bc2b5979850ea4a5fca1943a6", + "url": "https://inspirehep.net/files/ca453d4bc2b5979850ea4a5fca1943a6", + "label": "fig:result", + "source": "arxiv", + "caption": "The 90\\% C.L. limits on the pseudoscalar particles decaying to $e^+e^-$ pairs. On the right vertical axis we use the standard notation for the pseudo-scalar coupling $\\xi_e=\\epsilon (V/m_e) \\sqrt{4 \\pi \\alpha_{QED}}$, where $V=246$~GeV is a vacuum expectation value of the Higgs field \\cite{Andreas:2010ms}. This corresponds to the Lagrangian term $\\mathcal{L}\\supset -i \\xi_e \\frac{m_e}{V} a \\bar{\\psi}_e \\gamma_5 \\psi_e$. The red vertical line corresponds to the ATOMKI anomaly at $m_a = 16.7$ MeV (central value of the first result on berillium). The $\\epsilon$ range excluded at this mass is $2.1\\times 10^{-4} < \\epsilon < 3.2\\times 10^{-4}$. The region excluded using only the data collected with the visible mode geometry is denoted as \"NA64 vis.\", the extention of this region obtained using all data is denoted as \"NA64 invis.\". The regions excluded by the $(g-2)_e$ measurements (Berkley \\cite{Parker191} and LKB \\cite{finestructure2020}) are shown. The limits from the electron beam-dump experiments E774~\\cite{bross} and Orsay~\\cite{dav} are taken from Ref.~\\cite{Andreas:2010ms}.", + "filename": "X_linear_Kirpich_visible_pseudoscalar.png", + "material": "preprint" + } + ], + "license": [ + { + "url": "https://creativecommons.org/licenses/by/4.0/", + "license": "CC BY 4.0", + "material": "publication" + }, + { + "url": "http://creativecommons.org/licenses/by/4.0/", + "license": "CC BY 4.0", + "material": "preprint" + } + ], + "texkeys": [ + "NA64:2021aiq", + "NA64:2021ked", + "Andreev:2021syk" + ], + "citeable": true, + "imprints": [ + { + "date": "2021-12-01" + } + ], + "keywords": [ + { + "value": "electron: beam", + "schema": "INSPIRE" + }, + { + "value": "new physics: search for", + "schema": "INSPIRE" + }, + { + "value": "new particle", + "schema": "INSPIRE" + }, + { + "value": "cross section: difference", + "schema": "INSPIRE" + }, + { + "value": "dimension: 2", + "schema": "INSPIRE" + }, + { + "value": "nucleus: transition", + "schema": "INSPIRE" + }, + { + "value": "statistics", + "schema": "INSPIRE" + }, + { + "value": "anomaly", + "schema": "INSPIRE" + }, + { + "value": "pseudoscalar particle: leptonic decay", + "schema": "INSPIRE" + }, + { + "value": "electron: pair production", + "schema": "INSPIRE" + }, + { + "value": "electron: coupling", + "schema": "INSPIRE" + }, + { + "value": "pseudoscalar particle: coupling", + "schema": "INSPIRE" + }, + { + "value": "pseudoscalar particle: mass", + "schema": "INSPIRE" + }, + { + "value": "pseudoscalar", + "schema": "INSPIRE" + }, + { + "value": "CERN SPS", + "schema": "INSPIRE" + }, + { + "value": "vector boson: mass", + "schema": "INSPIRE" + }, + { + "value": "accelerator", + "schema": "INSPIRE" + }, + { + "value": "efficiency", + "schema": "INSPIRE" + }, + { + "value": "axion-like particles", + "schema": "INSPIRE" + }, + { + "value": "sensitivity", + "schema": "INSPIRE" + }, + { + "value": "statistical analysis", + "schema": "INSPIRE" + }, + { + "value": "parameter space", + "schema": "INSPIRE" + }, + { + "value": "background", + "schema": "INSPIRE" + }, + { + "value": "lifetime: difference", + "schema": "INSPIRE" + }, + { + "value": "beryllium", + "schema": "INSPIRE" + }, + { + "value": "experimental results", + "schema": "INSPIRE" + }, + { + "value": "talk: Prague 2020/07/30", + "schema": "INSPIRE" + }, + { + "value": "dark matter: direct production", + "schema": "INSPIRE" + }, + { + "value": "gauge boson: postulated particle", + "schema": "INSPIRE" + }, + { + "value": "gauge boson: leptonic decay", + "schema": "INSPIRE" + }, + { + "value": "pseudoscalar particle", + "schema": "INSPIRE" + }, + { + "value": "data analysis method", + "schema": "INSPIRE" + }, + { + "value": "S029AXE", + "schema": "PDG" + } + ], + "refereed": true, + "abstracts": [ + { + "value": "We report the results of a search for a light pseudoscalar particle a that couples to electrons and decays to e+e− performed using the high-energy CERN SPS H4 electron beam. If such light pseudoscalar exists, it could explain the ATOMKI anomaly (an excess of e+e− pairs in the nuclear transitions of 8Be and 4He nuclei at the invariant mass ≃17  MeV observed by the experiment at the 5 MV Van de Graaff accelerator at ATOMKI, Hungary). We used the NA64 data collected in the “visible mode” configuration with a total statistics corresponding to 8.4×1010 electrons on target (EOT) in 2017 and 2018. In order to increase sensitivity to small coupling parameter ε we also used the data collected in 2016–2018 in the “invisible mode” configuration of NA64 with a total statistics corresponding to 2.84×1011 EOT. The background and efficiency estimates for these two configurations were retained from our previous analyses searching for light vector bosons and axionlike particles (ALP) (the latter were assumed to couple predominantly to γ). In this work we recalculate the signal yields, which are different due to different cross section and lifetime of a pseudoscalar particle a, and perform a new statistical analysis. As a result, the region of the two dimensional parameter space ma−ε in the mass range from 1 to 17.1 MeV is excluded. At the mass of the central value of the ATOMKI anomaly (the first result obtained on the beryllium nucleus, 16.7 MeV) the values of ε in the range 2.1×10−4<ε<3.2×10−4 are excluded.", + "source": "APS" + }, + { + "value": "We report the results of a search for a light pseudoscalar particle $a$ that couples to electrons and decays to $e^+e^-$ performed using the high-energy CERN SPS H4 electron beam. If such pseudoscalar with a mass $\\simeq 17$ MeV exists, it could explain the ATOMKI anomaly. We used the NA64 data samples collected in the \"visible mode\" configuration with total statistics corresponding to $8.4\\times 10^{10}$ electrons on target (EOT) in 2017 and 2018. In order to increase sensitivity to small coupling parameter $\\epsilon$ we used also the data collected in 2016-2018 in the \"invisible mode\" configuration of NA64 with a total statistics corresponding to $2.84\\times 10^{11}$ EOT. A thorough analysis of both these data samples in the sense of background and efficiency estimations was already performed and reported in our previous papers devoted to the search for light vector particles and axion-like particles (ALP). In this work we recalculate the signal yields, which are different due to different cross section and life time of a pseudoscalar particle $a$, and perform a new statistical analysis. As a result, the region of the two dimensional parameter space $m_a - \\epsilon$ in the mass range from 1 to 17.1 MeV is excluded. At the mass of the ATOMKI anomaly the values of $\\epsilon$ in the range $2.1 \\times 10^{-4} < \\epsilon < 3.2 \\times 10^{-4}$ are excluded.", + "source": "arXiv" + } + ], + "copyright": [ + { + "year": 2021, + "holder": "authors", + "material": "publication" + } + ], + "public_notes": [ + { + "value": "6 pages, 2 figures", + "source": "arXiv" + } + ], + "arxiv_eprints": [ + { + "value": "2104.13345", + "categories": [ + "hep-ex" + ] + } + ], + "document_type": [ + "article", + "conference paper" + ], + "preprint_date": "2021-04-27", + "collaborations": [ + { + "value": "NA64", + "record": { + "$ref": "https://inspirehep.net/api/experiments/1479798" + } + } + ], + "control_number": 1992535, + "legacy_version": "20210526164834.0", + "deleted_records": [ + { + "$ref": "https://inspirehep.net/api/literature/1860981" + } + ], + "number_of_pages": 5, + "inspire_categories": [ + { + "term": "Experiment-HEP", + "source": "curator" + }, + { + "term": "Experiment-HEP", + "source": "arxiv" + }, + { + "term": "Experiment-HEP" + } + ], + "legacy_creation_date": "2021-04-28", + "accelerator_experiments": [ + { + "record": { + "$ref": "https://inspirehep.net/api/experiments/1479798" + }, + "legacy_name": "CERN-NA-064" + } + ], + "external_system_identifiers": [ + { + "value": "2765541", + "schema": "CDS" + }, + { + "value": "2798711", + "schema": "CDS" + } + ] + }, + "uuid": "efbeb951-5ef3-4399-8d9a-0a2ce7a5e8c5", + "created": "2021-04-28T00:00:00+00:00" + } + ] + } +} \ No newline at end of file diff --git a/site/tests/inspire_harvester/data/record_with_scoap3_art.json b/site/tests/inspire_harvester/data/record_with_scoap3_art.json new file mode 100644 index 00000000..a5b34c73 --- /dev/null +++ b/site/tests/inspire_harvester/data/record_with_scoap3_art.json @@ -0,0 +1,354 @@ +{ + "hits": { + "total": 1, + "hits": [ + { + "links": { + "bibtex": "https://inspirehep.net/api/literature/1517785?format=bibtex", + "latex-eu": "https://inspirehep.net/api/literature/1517785?format=latex-eu", + "latex-us": "https://inspirehep.net/api/literature/1517785?format=latex-us", + "json": "https://inspirehep.net/api/literature/1517785?format=json", + "json-expanded": "https://inspirehep.net/api/literature/1517785?format=json-expanded", + "cv": "https://inspirehep.net/api/literature/1517785?format=cv", + "citations": "https://inspirehep.net/api/literature/?q=refersto%3Arecid%3A1517785" + }, + "revision_id": 476, + "updated": "2023-03-07T02:14:00.298542+00:00", + "metadata": { + "citation_count_without_self_citations": 87, + "publication_info": [ + { + "year": 2017, + "artid": "119", + "page_start": "119", + "journal_title": "JHEP", + "journal_record": { + "$ref": "https://inspirehep.net/api/journals/1213103" + }, + "journal_volume": "06" + }, + { + "year": 2017, + "artid": "061", + "material": "erratum", + "page_start": "061", + "journal_title": "JHEP", + "journal_record": { + "$ref": "https://inspirehep.net/api/journals/1213103" + }, + "journal_volume": "10" + } + ], + "report_numbers": [ + { + "value": "CERN-TH-2017-054" + }, + { + "value": "TTP17-005" + }, + { + "value": "MITP/17-011" + } + ], + "citation_count": 105, + "documents": [ + { + "key": "62fac6516fe3c0b56c35c1fe77a28087", + "url": "https://inspirehep.net/files/62fac6516fe3c0b56c35c1fe77a28087", + "source": "SCOAP3", + "filename": "scoap", + "description": "Article from SCOAP3" + } + ], + "core": true, + "dois": [ + { + "value": "10.1007/JHEP06(2017)119", + "source": "bibmatch" + }, + { + "value": "10.1007/JHEP10(2017)061", + "source": "bibmatch", + "material": "erratum" + } + ], + "titles": [ + { + "title": "Closing the window for compressed Dark Sectors with disappearing charged tracks", + "source": "arXiv" + } + ], + "$schema": "https://inspirehep.net/schemas/records/hep.json", + "authors": [ + { + "ids": [ + { + "value": "INSPIRE-00028859", + "schema": "INSPIRE ID" + } + ], + "uuid": "c2c96a64-610b-4c1b-beb7-55a849b83396", + "record": { + "$ref": "https://inspirehep.net/api/authors/1041997" + }, + "full_name": "Mahbubani, Rakhi", + "affiliations": [ + { + "value": "CERN", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902725" + } + }, + { + "value": "Ecole Polytechnique, Lausanne", + "record": { + "$ref": "https://inspirehep.net/api/institutions/912522" + } + } + ], + "signature_block": "MABABANr", + "raw_affiliations": [ + { + "value": "Theoretical Physics Department - CERN - Geneva - Switzerland" + }, + { + "value": "Theoretical Particle Physics Laboratory - Institute of Physics - EPFL - Lausanne - Switzerland" + } + ], + "affiliations_identifiers": [ + { + "value": "grid.9132.9", + "schema": "GRID" + }, + { + "value": "grid.5333.6", + "schema": "GRID" + } + ] + }, + { + "uuid": "42992663-adfb-4db6-a049-c1798a48e327", + "record": { + "$ref": "https://inspirehep.net/api/authors/1057378" + }, + "full_name": "Schwaller, Pedro", + "affiliations": [ + { + "value": "U. Mainz, PRISMA", + "record": { + "$ref": "https://inspirehep.net/api/institutions/1280366" + } + }, + { + "value": "Mainz U., Inst. Phys.", + "record": { + "$ref": "https://inspirehep.net/api/institutions/902982" + } + } + ], + "signature_block": "SWALARp", + "curated_relation": true, + "raw_affiliations": [ + { + "value": "PRISMA Cluster of Excellence & Mainz Institute for Theoretical Physics - Johannes Gutenberg U. - 55099 - Mainz - Germany" + } + ], + "affiliations_identifiers": [ + { + "value": "grid.5802.f", + "schema": "GRID" + } + ] + }, + { + "uuid": "ce9580b3-ba64-4c7c-9db2-35222504524d", + "record": { + "$ref": "https://inspirehep.net/api/authors/1037623" + }, + "full_name": "Zurita, Jose", + "affiliations": [ + { + "value": "KIT, Karlsruhe, IKP", + "record": { + "$ref": "https://inspirehep.net/api/institutions/1406039" + } + } + ], + "signature_block": "ZARATj", + "curated_relation": true, + "raw_affiliations": [ + { + "value": "Institute for Nuclear Physics (IKP) - Karlsruhe Institute of Technology - Hermann-von-Helmholtz-Platz 1 - D-76344 - Eggenstein-Leopoldshafen - Germany" + }, + { + "value": "Institute for Theoretical Particle Physics (TTP) - Karlsruhe Institute of Technology - Engesserstraße 7 - D-76128 - Karlsruhe - Germany" + } + ], + "affiliations_identifiers": [ + { + "value": "grid.7892.4", + "schema": "GRID" + } + ] + } + ], + "curated": true, + "figures": [ + { + "key": "bf386a98e86e4342b847c2a90517e8db", + "url": "https://inspirehep.net/files/bf386a98e86e4342b847c2a90517e8db", + "source": "arxiv", + "filename": "Plots_C1_BR_sum.png" + }, + { + "key": "63dd8f943e4c73827d05b811d05344b3", + "url": "https://inspirehep.net/files/63dd8f943e4c73827d05b811d05344b3", + "source": "arxiv", + "caption": "Caption not extracted", + "filename": "Plots_C1_lifetime_Higgsino.png" + } + ], + "license": [ + { + "url": "http://arxiv.org/licenses/nonexclusive-distrib/1.0/", + "imposing": "arXiv" + }, + { + "license": "CC-BY-4.0" + } + ], + "texkeys": [ + "Mahbubani:2017gjh" + ], + "citeable": true, + "imprints": [ + { + "date": "2017-06-22" + } + ], + "keywords": [ + { + "value": "Beyond Standard Model", + "source": "author" + }, + { + "value": "Supersymmetric Standard Model", + "source": "author" + }, + { + "value": "new physics", + "schema": "INSPIRE" + }, + { + "value": "supersymmetry: dark matter", + "schema": "INSPIRE" + }, + { + "value": "minimal supersymmetric standard model", + "schema": "INSPIRE" + }, + { + "value": "CERN LHC Coll", + "schema": "INSPIRE" + }, + { + "value": "p p: scattering", + "schema": "INSPIRE" + }, + { + "value": "sparticle: mass spectrum", + "schema": "INSPIRE" + }, + { + "value": "sparticle: heavy", + "schema": "INSPIRE" + }, + { + "value": "sparticle: signature", + "schema": "INSPIRE" + }, + { + "value": "sparticle: invisible decay", + "schema": "INSPIRE" + }, + { + "value": "Higgsino: dark matter", + "schema": "INSPIRE" + }, + { + "value": "Higgsino: mass", + "schema": "INSPIRE" + }, + { + "value": "Higgsino: LSP", + "schema": "INSPIRE" + }, + { + "value": "LSP: dark matter", + "schema": "INSPIRE" + }, + { + "value": "dark matter: relic density", + "schema": "INSPIRE" + }, + { + "value": "relic density: thermal", + "schema": "INSPIRE" + } + ], + "refereed": true, + "abstracts": [ + { + "value": "We investigate the sensitivity at current and future hadron colliders to a heavy electrically-charged particle with a proper decay length below a centimetre, whose decay products are invisible due to below-threshold energies and/or small couplings to the Standard Model. A cosmologically-motivated example of a framework that contains such a particle is the Minimal Supersymmetric Standard Model in the limit of pure Higgsinos. The current hadron-collider search strategy has no sensitivity to the upper range of pure-Higgsino masses that are consistent with the thermal relic density, even at a future collider with 100 TeV centre-of-mass energy. We show that performing a disappearing track search within the inner 10 cm of detector volume would improve the reach in lifetime by a factor of 3 at the 14 TeV LHC and a further factor of 5 at a 100 TeV collider, resulting in around 10 events for 1.1 TeV thermal Higgsinos. In order to include the particles with the largest boost in the analysis, we furthermore propose a purely track-based search in both the central and forward regions, each of which would increase the number of events by another factor of 5, improving our reach at small lifetimes. This would allow us to definitively discover or exclude the experimentally-elusive pure-Higgsino thermal relic at a 100 TeV collider. Our results illustrate the importance of varying detector design when assessing the reach of future high energy colliders.", + "source": "Springer" + }, + { + "value": "We investigate the sensitivity at current and future hadron colliders to a heavy electrically-charged particle with a proper decay length below a centimetre, whose decay products are invisible due to below-threshold energies and/or small couplings to the Standard Model. A cosmologically-motivated example of a framework that contains such a particle is the Minimal Supersymmetric Standard Model in the limit of pure Higgsinos. The current hadron-collider search strategy has no sensitivity to the upper range of pure Higgsino masses that are consistent with the thermal relic density, even at a future collider with 100 TeV centre-of-mass energy. We show that performing a disappearing track search within the inner 10 cm of detector volume would improve the reach in lifetime by a factor of 3 at the 14 TeV LHC and a further factor of 5 at a 100 TeV collider, resulting in around 10 events for 1.1 TeV thermal Higgsinos. In order to include the particles with the largest boost in the analysis, we furthermore propose a purely track-based search in both the central and forward regions, each of which would increase the number of events by another factor of 5, improving our reach at small lifetimes. This would allow us to definitively discover or exclude the experimentally-elusive pure-Higgsino thermal relic at a 100 TeV collider.", + "source": "arXiv" + } + ], + "public_notes": [ + { + "value": "20 pages, 11 figures", + "source": "arXiv" + } + ], + "arxiv_eprints": [ + { + "value": "1703.05327", + "categories": [ + "hep-ph" + ] + } + ], + "document_type": [ + "article" + ], + "preprint_date": "2017-03-15", + "control_number": 1517785, + "legacy_version": "20181120155002.0", + "number_of_pages": 22, + "inspire_categories": [ + { + "term": "Phenomenology-HEP" + } + ], + "legacy_creation_date": "2017-03-17", + "external_system_identifiers": [ + { + "value": "2017JHEP...06..119M", + "schema": "ADS" + }, + { + "value": "2259331", + "schema": "CDS" + } + ] + }, + "id": "1517785", + "created": "2017-03-17T00:00:00+00:00", + "uuid": "cc84bf36-5990-453a-916e-e76c3858e5c8" + } + ] + } +} \ No newline at end of file diff --git a/site/tests/inspire_harvester/test_transformer.py b/site/tests/inspire_harvester/test_transformer.py index c1fae5b7..3acdaefd 100644 --- a/site/tests/inspire_harvester/test_transformer.py +++ b/site/tests/inspire_harvester/test_transformer.py @@ -192,9 +192,8 @@ def test_transform_document_type_multiple(running_app): result = transformer._transform_document_type() - assert result is None - assert len(transformer.metadata_errors) == 1 - assert "Multiple document types found" in transformer.metadata_errors[0] + # found thesis - should take over + assert result == {"id": "publication-dissertation"} def test_transform_document_type_unmapped(running_app): @@ -304,21 +303,21 @@ def test_transform_creators(): "last_name": "Smith", "inspire_roles": ["supervisor"], }, - ] + ], + "corporate_author": ["CERN", "NASA"], }, } transformer = Inspire2RDM(inspire_record) - with patch.object(transformer, "_transform_creatibutors") as mock_transform: - mock_transform.return_value = [{"person_or_org": {"name": "Doe, John"}}] - - result = transformer._transform_creators() + result = transformer._transform_creators() - # Should only include authors, not supervisors - mock_transform.assert_called_once() - called_authors = mock_transform.call_args[0][0] - assert len(called_authors) == 1 - assert called_authors[0]["inspire_roles"] == ["author"] + # Check corporate authors + corporate_authors = [ + c for c in result if c["person_or_org"]["type"] == "organizational" + ] + assert len(corporate_authors) == 2 + assert corporate_authors[0]["person_or_org"]["name"] == "CERN" + assert corporate_authors[1]["person_or_org"]["name"] == "NASA" def test_transform_contributors(): @@ -338,38 +337,19 @@ def test_transform_contributors(): "inspire_roles": ["supervisor"], }, ], - "corporate_author": ["CERN", "NASA"], }, } transformer = Inspire2RDM(inspire_record) - with patch.object(transformer, "_transform_creatibutors") as mock_transform: - mock_transform.return_value = [ - { - "person_or_org": { - "type": "personal", - "name": "Smith, Jane", - "role": {"id": "other"}, - } - }, - ] - - result = transformer._transform_contributors() + result = transformer._transform_contributors() - # Should include supervisors and corporate authors - assert len(result) == 3 # 1 supervisor + 2 corporate authors - - # Check corporate authors - corporate_contributors = [ - c for c in result if c["person_or_org"]["type"] == "organizational" - ] - assert len(corporate_contributors) == 2 - assert corporate_contributors[0]["person_or_org"]["name"] == "CERN" - assert corporate_contributors[1]["person_or_org"]["name"] == "NASA" + # Should include supervisors and corporate authors + assert len(result) == 1 # 1 supervisor (author went to creators) def test_transform_creatibutors(): """Test _transform_creatibutors.""" + authors = [ { "first_name": "John", @@ -383,27 +363,18 @@ def test_transform_creatibutors(): inspire_record = {"id": "12345", "metadata": {}} transformer = Inspire2RDM(inspire_record) - with ( - patch.object(transformer, "_transform_author_affiliations") as mock_aff, - patch.object(transformer, "_transform_author_identifiers") as mock_ids, - ): - mock_aff.return_value = [{"name": "CERN"}] - mock_ids.return_value = [ - {"identifier": "0000-0000-0000-0000", "scheme": "orcid"} - ] + result = transformer._transform_creatibutors(authors) - result = transformer._transform_creatibutors(authors) - - assert len(result) == 1 - author = result[0] - assert author["person_or_org"]["given_name"] == "John" - assert author["person_or_org"]["family_name"] == "Doe" - assert author["person_or_org"]["name"] == "Doe, John" - assert author["role"] == "author" - assert author["affiliations"] == [{"name": "CERN"}] - assert author["person_or_org"]["identifiers"] == [ - {"identifier": "0000-0000-0000-0000", "scheme": "orcid"} - ] + assert len(result) == 1 + author = result[0] + assert author["person_or_org"]["given_name"] == "John" + assert author["person_or_org"]["family_name"] == "Doe" + assert author["person_or_org"]["name"] == "Doe, John" + assert author["role"] == "author" + assert author["affiliations"] == [{"name": "CERN"}] + assert author["person_or_org"]["identifiers"] == [ + {"identifier": "0000-0000-0000-0000", "scheme": "orcid"} + ] def test_transform_author_identifiers(): diff --git a/site/tests/inspire_harvester/test_update_create_logic.py b/site/tests/inspire_harvester/test_update_create_logic.py index 64deff6c..c9f8cd91 100644 --- a/site/tests/inspire_harvester/test_update_create_logic.py +++ b/site/tests/inspire_harvester/test_update_create_logic.py @@ -3,6 +3,7 @@ from time import sleep from unittest.mock import Mock, patch +import pytest from celery import current_app from invenio_access.permissions import system_identity from invenio_rdm_records.proxies import current_rdm_records_service @@ -12,17 +13,18 @@ from cds_rdm.legacy.resolver import get_record_by_version +from ..utils import add_file_to_draft from .utils import mock_requests_get, run_harvester_mock def test_new_non_CDS_record( - running_app, location, scientific_community, datastream_config + running_app, location, scientific_community, datastream_config ): """Test new non-CDS origin record.""" with open( - "tests/inspire_harvester/data/completely_new_inspire_rec.json", - "r", + "tests/inspire_harvester/data/completely_new_inspire_rec.json", + "r", ) as f: new_record = json.load(f) @@ -62,12 +64,12 @@ def test_new_non_CDS_record( def test_CDS_DOI_create_record_fails( - running_app, location, scientific_community, datastream_config + running_app, location, scientific_community, datastream_config ): """Test insert record with CDS DOI - no record matched (deleted?).""" with open( - "tests/inspire_harvester/data/record_with_cds_DOI.json", - "r", + "tests/inspire_harvester/data/record_with_cds_DOI.json", + "r", ) as f: new_record = json.load(f) @@ -87,7 +89,7 @@ def test_CDS_DOI_create_record_fails( def test_update_record_with_CDS_DOI_one_doc_type( - running_app, location, scientific_community, minimal_record, datastream_config + running_app, location, scientific_community, minimal_record, datastream_config ): """Test update record with CDS DOI - matched record. @@ -100,8 +102,8 @@ def test_update_record_with_CDS_DOI_one_doc_type( record = current_rdm_records_service.publish(system_identity, draft.id) with open( - "tests/inspire_harvester/data/record_with_cds_DOI.json", - "r", + "tests/inspire_harvester/data/record_with_cds_DOI.json", + "r", ) as f: new_record = json.load(f) new_record["hits"]["hits"][0]["metadata"]["dois"] = [ @@ -130,28 +132,28 @@ def test_update_record_with_CDS_DOI_one_doc_type( original_record = current_rdm_records_service.read(system_identity, record["id"]) assert original_record._record.versions.latest_index == 2 new_version = get_record_by_version(original_record.data["parent"]["id"], 2) - assert new_version.data["metadata"]["resource_type"]["id"] == "publication-article" + assert new_version.data["metadata"]["resource_type"]["id"] == "publication-preprint" assert new_version.data["metadata"]["publication_date"] == "2018" assert new_version.data["metadata"]["title"] == "Upgrade Software and Computing" assert { - "identifier": "2707794", - "scheme": "inspire", - "relation_type": {"id": "isversionof"}, - "resource_type": {"id": "publication-other"}, - } in new_version.data["metadata"]["related_identifiers"] + "identifier": "2707794", + "scheme": "inspire", + "relation_type": {"id": "isvariantof"}, + "resource_type": {"id": "publication-other"}, + } in new_version.data["metadata"]["related_identifiers"] # clean up for other tests running_app.app.config["RDM_PERSISTENT_IDENTIFIERS"]["doi"]["required"] = False def test_update_record_with_CDS_DOI_multiple_doc_types( - running_app, location, scientific_community, minimal_record, datastream_config + running_app, location, scientific_community, minimal_record, datastream_config ): pass def test_update_migrated_record_with_CDS_DOI( - running_app, location, scientific_community + running_app, location, scientific_community ): """Test update record with CDS DOI - should raise exception to handle manually.""" passed = False @@ -162,22 +164,95 @@ def test_update_no_CDS_DOI_one_doc_type(running_app, location, scientific_commun passed = False -def test_update_no_CDS_DOI_multiple_doc_types(running_app, location, - scientific_community, - datastream_config, minimal_record): +def test_update_no_CDS_DOI_multiple_doc_types( + running_app, location, scientific_community, datastream_config, minimal_record_with_files +): + service = current_rdm_records_service - minimal_record["metadata"]["resource_type"] = {"id": "publication-preprint"} - minimal_record["metadata"]["related_identifiers"] = [{ - "identifier": "2104.13342", - "scheme": "arxiv", - "relation_type": {"id": "isversionof"}, - "resource_type": {"id": "publication-other"}, - }] + minimal_record_with_files["metadata"]["resource_type"] = {"id": "publication-preprint"} + minimal_record_with_files["metadata"]["related_identifiers"] = [ + { + "identifier": "2104.13342", + "scheme": "arxiv", + "relation_type": {"id": "isversionof"}, + "resource_type": {"id": "publication-other"}, + } + ] - draft = current_rdm_records_service.create(system_identity, minimal_record) + draft = service.create(system_identity, minimal_record_with_files) + add_file_to_draft(service.draft_files, system_identity, draft, "test") + record = current_rdm_records_service.publish(system_identity, draft.id) + + RDMRecord.index.refresh() + with open( + "tests/inspire_harvester/data/record_with_no_cds_DOI_multiple_doc_type.json", + "r", + ) as f: + new_record = json.load(f) + + mock_record = partial(mock_requests_get, mock_content=new_record) + RDMRecord.index.refresh() + run_harvester_mock(datastream_config, mock_record) + RDMRecord.index.refresh() + + record = current_rdm_records_service.read(system_identity, record["id"]) + # from preprint to conference paper + assert record.data["metadata"]["resource_type"]["id"] == "publication-conferencepaper" + # ensure we didn't create a new version + assert record._record.versions.latest_index == 1 + # check title updated + assert record.data["metadata"]["title"] == "Search for pseudoscalar bosons decaying into $e^+e^-$ pairs in the NA64 experiment at the CERN SPS" + # check files replaced + # when we manage non-CDS record - we trust INSPIRE as a source of truth + # therefore files will be synced 1:1 with INSPIRE + assert "PhysRevD.104.L111102.pdf" in record._record.files.entries + assert len(record._record.files.entries.items()) == 1 + + +@pytest.mark.skip(reason="metadata only to files transformation is broken") +def test_update_no_CDS_DOI_from_metadata_only_to_files( + running_app, location, scientific_community, datastream_config, minimal_record +): + """Test update record, originally no files, adding files to the same version.""" + service = current_rdm_records_service + + minimal_record["metadata"]["resource_type"] = { + "id": "publication-preprint"} + minimal_record["metadata"]["related_identifiers"] = [ + { + "identifier": "2104.13345", + "scheme": "arxiv", + "relation_type": {"id": "isversionof"}, + "resource_type": {"id": "publication-other"}, + } + ] + + draft = service.create(system_identity, minimal_record) record = current_rdm_records_service.publish(system_identity, draft.id) + + RDMRecord.index.refresh() with open( - "tests/inspire_harvester/data/record_no_cds_DOI_multiple_doc_type.json", + "tests/inspire_harvester/data/record_with_no_cds_DOI_multiple_doc_type2.json", "r", ) as f: new_record = json.load(f) + + mock_record = partial(mock_requests_get, mock_content=new_record) + RDMRecord.index.refresh() + run_harvester_mock(datastream_config, mock_record) + RDMRecord.index.refresh() + + record = current_rdm_records_service.read(system_identity, record["id"]) + # from preprint to conference paper + assert record.data["metadata"]["resource_type"][ + "id"] == "publication-conferencepaper" + # ensure we didn't create a new version + assert record._record.versions.latest_index == 1 + # check title updated + assert record.data["metadata"][ + "title"] == "Search for pseudoscalar bosons decaying into $e^+e^-$ pairs in the NA64 experiment at the CERN SPS" + # check files replaced + # when we manage non-CDS record - we trust INSPIRE as a source of truth + # therefore files will be synced 1:1 with INSPIRE + assert "PhysRevD.104.L111102.pdf" in record._record.files.entries + assert len(record._record.files.entries.items()) == 1 diff --git a/site/tests/inspire_harvester/utils.py b/site/tests/inspire_harvester/utils.py index f620d315..65411761 100644 --- a/site/tests/inspire_harvester/utils.py +++ b/site/tests/inspire_harvester/utils.py @@ -6,6 +6,7 @@ # under the terms of the MIT License; see LICENSE file for more details. """Pytest utils module.""" +from io import BytesIO from unittest.mock import Mock, patch from celery import current_app diff --git a/site/tests/utils.py b/site/tests/utils.py new file mode 100644 index 00000000..d6d19694 --- /dev/null +++ b/site/tests/utils.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2025 CERN. +# +# CDS-RDM is free software; you can redistribute it and/or modify it under +# the terms of the MIT License; see LICENSE file for more details. + +"""Task tests.""" +from io import BytesIO + + +def add_file_to_draft(draft_file_service, identity, draft, file_id): + """Add file to draft record.""" + draft_file_service.init_files(identity, draft.id, data=[{"key": file_id}]) + draft_file_service.set_file_content( + identity, draft.id, file_id, BytesIO(b"test file content") + ) + draft_file_service.commit_file(identity, draft.id, file_id) \ No newline at end of file From cdf6516ddab997d5717fcfda133ee05213fe462e Mon Sep 17 00:00:00 2001 From: Karolina Przerwa Date: Sat, 10 Jan 2026 13:45:49 +0100 Subject: [PATCH 3/9] change(harvester): request header extended format --- site/cds_rdm/components.py | 4 +++- site/cds_rdm/inspire_harvester/reader.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/site/cds_rdm/components.py b/site/cds_rdm/components.py index d3bb82a0..bd56c688 100644 --- a/site/cds_rdm/components.py +++ b/site/cds_rdm/components.py @@ -99,7 +99,7 @@ class SubjectsValidationComponent(ServiceComponent): def _validate_subject_changes(self, identity, updated_data, original_data): """Validate that the subject changes are allowed.""" user = getattr(identity, "user", None) - if user and user.has_role("administration"): + if identity.id == "system" or user and user.has_role("administration"): return updated_collection_subjects = { s["subject"] @@ -111,6 +111,8 @@ def _validate_subject_changes(self, identity, updated_data, original_data): for s in original_data if s.get("subject", "").startswith("collection:") } + print(updated_collection_subjects, "=============") + print(original_collection_subjects, "++++++++++++++++") if updated_collection_subjects != original_collection_subjects: raise ValidationError( "Collection subjects cannot be updated.", diff --git a/site/cds_rdm/inspire_harvester/reader.py b/site/cds_rdm/inspire_harvester/reader.py index b0dab630..3363c3bd 100644 --- a/site/cds_rdm/inspire_harvester/reader.py +++ b/site/cds_rdm/inspire_harvester/reader.py @@ -38,7 +38,8 @@ def __init__( def _iter(self, url, *args, **kwargs): """Yields HTTP response.""" - headers = {"Accept": "application/json"} + # header set to include additional data (external file URLs and more detailed metadata + headers = {"Accept": "application/vnd+inspire.record.expanded+json"} while url: # Continue until there is no "next" link current_app.logger.info(f"Querying URL: {url}.") From ba7b07e65b0996ce830167b8f4c0f321ab6c30c1 Mon Sep 17 00:00:00 2001 From: Karolina Przerwa Date: Sat, 10 Jan 2026 13:46:41 +0100 Subject: [PATCH 4/9] change(harvester): refactor transformer's architecture --- .../inspire_harvester/transform/__init__.py | 0 .../inspire_harvester/transform/config.py | 54 + .../inspire_harvester/transform/context.py | 18 + .../transform/mappers/__init__.py | 0 .../transform/mappers/basic_metadata.py | 237 ++++ .../transform/mappers/contributors.py | 142 ++ .../transform/mappers/custom_fields.py | 84 ++ .../transform/mappers/files.py | 56 + .../transform/mappers/identifiers.py | 203 +++ .../transform/mappers/mapper.py | 28 + .../transform/mappers/thesis.py | 47 + .../inspire_harvester/transform/policies.py | 41 + .../transform/resource_types.py | 162 +++ .../transform/transform_entry.py | 201 +++ .../inspire_harvester/transform/utils.py | 73 + .../inspire_harvester/transform_entry.py | 1173 ----------------- site/cds_rdm/inspire_harvester/transformer.py | 2 +- 17 files changed, 1347 insertions(+), 1174 deletions(-) create mode 100644 site/cds_rdm/inspire_harvester/transform/__init__.py create mode 100644 site/cds_rdm/inspire_harvester/transform/config.py create mode 100644 site/cds_rdm/inspire_harvester/transform/context.py create mode 100644 site/cds_rdm/inspire_harvester/transform/mappers/__init__.py create mode 100644 site/cds_rdm/inspire_harvester/transform/mappers/basic_metadata.py create mode 100644 site/cds_rdm/inspire_harvester/transform/mappers/contributors.py create mode 100644 site/cds_rdm/inspire_harvester/transform/mappers/custom_fields.py create mode 100644 site/cds_rdm/inspire_harvester/transform/mappers/files.py create mode 100644 site/cds_rdm/inspire_harvester/transform/mappers/identifiers.py create mode 100644 site/cds_rdm/inspire_harvester/transform/mappers/mapper.py create mode 100644 site/cds_rdm/inspire_harvester/transform/mappers/thesis.py create mode 100644 site/cds_rdm/inspire_harvester/transform/policies.py create mode 100644 site/cds_rdm/inspire_harvester/transform/resource_types.py create mode 100644 site/cds_rdm/inspire_harvester/transform/transform_entry.py create mode 100644 site/cds_rdm/inspire_harvester/transform/utils.py delete mode 100644 site/cds_rdm/inspire_harvester/transform_entry.py diff --git a/site/cds_rdm/inspire_harvester/transform/__init__.py b/site/cds_rdm/inspire_harvester/transform/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/site/cds_rdm/inspire_harvester/transform/config.py b/site/cds_rdm/inspire_harvester/transform/config.py new file mode 100644 index 00000000..3ad44e1f --- /dev/null +++ b/site/cds_rdm/inspire_harvester/transform/config.py @@ -0,0 +1,54 @@ +from enum import Enum + +from cds_rdm.inspire_harvester.transform.mappers.basic_metadata import \ + ResourceTypeMapper, TitleMapper, AdditionalTitlesMapper, PublisherMapper, \ + PublicationDateMapper, CopyrightMapper, DescriptionMapper, \ + AdditionalDescriptionsMapper, SubjectsMapper, LanguagesMapper +from cds_rdm.inspire_harvester.transform.mappers.contributors import AuthorsMapper, \ + ContributorsMapper +from cds_rdm.inspire_harvester.transform.mappers.custom_fields import ImprintMapper, \ + CERNFieldsMapper +from cds_rdm.inspire_harvester.transform.mappers.files import FilesMapper +from cds_rdm.inspire_harvester.transform.mappers.identifiers import DOIMapper, \ + IdentifiersMapper, RelatedIdentifiersMapper +from cds_rdm.inspire_harvester.transform.mappers.thesis import ThesisDefenceDateMapper, \ + ThesisPublicationDateMapper +from cds_rdm.inspire_harvester.transform.policies import MapperPolicy +from cds_rdm.inspire_harvester.transform.resource_types import ResourceType + +BASE_MAPPERS = (FilesMapper(), + ResourceTypeMapper(), + DOIMapper(), + TitleMapper(), + AdditionalTitlesMapper(), + AuthorsMapper(), + ContributorsMapper(), + PublisherMapper(), + PublicationDateMapper(), + CopyrightMapper(), + DescriptionMapper(), + AdditionalDescriptionsMapper(), + SubjectsMapper(), + LanguagesMapper(), + ImprintMapper(), + CERNFieldsMapper(), + IdentifiersMapper(), + RelatedIdentifiersMapper() + ) +THESIS_MAPPERS = (ThesisDefenceDateMapper(),) + +inspire_mapper_policy = MapperPolicy(base=BASE_MAPPERS) + +mapper_policy = MapperPolicy( + base=BASE_MAPPERS, + add={ + ResourceType.THESIS: THESIS_MAPPERS, + }, + replace={ + (ResourceType.THESIS, "metadata.publication_date"): + ThesisPublicationDateMapper(), + # (ResourceType.ARTICLE, "metadata.publication_info"): ArticlePublicationInfoMapper(), + # (ResourceType.CONFERENCE_PAPER, "metadata.publication_info"): ConferencePublicationInfoMapper(), + }, + # if you had a generic publication_info mapper in base, you'd replace it here +) diff --git a/site/cds_rdm/inspire_harvester/transform/context.py b/site/cds_rdm/inspire_harvester/transform/context.py new file mode 100644 index 00000000..2b043332 --- /dev/null +++ b/site/cds_rdm/inspire_harvester/transform/context.py @@ -0,0 +1,18 @@ +from dataclasses import dataclass, field + +from typing import List + +from cds_rdm.inspire_harvester.transform.resource_types import ResourceType + + +@dataclass(frozen=True) +class MetadataSerializationContext: + resource_type: ResourceType + inspire_id: str + errors: List[str] = field(default_factory=list) + + # def __init__(self, resource_type, inspire_id): + # self.resource_type = resource_type + # self.inspire_id = inspire_id + # self.logger = Logger(inspire_id=self.inspire_id) + diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/__init__.py b/site/cds_rdm/inspire_harvester/transform/mappers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/basic_metadata.py b/site/cds_rdm/inspire_harvester/transform/mappers/basic_metadata.py new file mode 100644 index 00000000..f42476b4 --- /dev/null +++ b/site/cds_rdm/inspire_harvester/transform/mappers/basic_metadata.py @@ -0,0 +1,237 @@ +import pycountry +from babel_edtf import parse_edtf +from dataclasses import dataclass +from flask import current_app +from edtf.parser.grammar import ParseException + +from cds_rdm.inspire_harvester.transform.mappers.mapper import MapperBase + + +@dataclass(frozen=True) +class ResourceTypeMapper(MapperBase): + id = "metadata.resource_type.id" + + def map_value(self, src_metadata, ctx, logger): + + return ctx.resource_type.value + + +@dataclass(frozen=True) +class TitleMapper(MapperBase): + id = "metadata.title" + + def map_value(self, src_metadata, ctx, logger): + inspire_titles = src_metadata.get("titles", []) + return inspire_titles[0].get("title") + + +@dataclass(frozen=True) +class AdditionalTitlesMapper(MapperBase): + id = "metadata.additional_titles" + + def map_value(self, src_metadata, ctx, logger): + inspire_titles = src_metadata.get("titles", []) + rdm_additional_titles = [] + for i, inspire_title in enumerate(inspire_titles[1:]): + try: + + alt_title = { + "title": inspire_title.get("title"), + "type": { + "id": "alternative-title", + }, + } + rdm_additional_titles.append(alt_title) + if inspire_title.get("subtitle"): + subtitle = { + "title": inspire_title.get("subtitle"), + "type": { + "id": "subtitle", + }, + } + rdm_additional_titles.append(subtitle) + except Exception as e: + ctx.errors.append( + f"Title {inspire_title} transform failed. INSPIRE#{ctx.inspire_id}. Error: {e}." + ) + return rdm_additional_titles + + + +@dataclass(frozen=True) +class PublisherMapper(MapperBase): + id = "metadata.publisher" + + def validate(self, src, ctx): + imprints = src.get("imprints", []) + + if len(imprints) > 1: + ctx.errors.append( + f"More than 1 imprint found. INSPIRE#{ctx.inspire_id}." + ) + + def map_value(self, src_metadata, ctx, logger): + imprints = src_metadata.get("imprints", []) + imprint = None + publisher = None + if imprints: + imprint = imprints[0] + publisher = imprint.get("publisher") + + DATACITE_PREFIX = current_app.config["DATACITE_PREFIX"] + dois = src_metadata.get("dois", []) + + has_cds_doi = next( + (d["value"] for d in dois if d["value"].startswith(DATACITE_PREFIX)), + False, + ) + if has_cds_doi and not publisher: + return "CERN" + return publisher + + +@dataclass(frozen=True) +class PublicationDateMapper(MapperBase): + + id = "metadata.publication_date" + + def map_value(self, src_metadata, ctx, logger): + imprints = src_metadata.get("imprints", []) + imprint_date = imprints[0].get("date") + + publication_date = src_metadata.get("publication_info", {}).get("year") + + creation_date = src_metadata.get("created") + + date = publication_date or imprint_date or creation_date + try: + parsed_date = str(parse_edtf(date)) + return parsed_date + except ParseException as e: + ctx.errors.append( + f"Publication date transformation failed." + f"INSPIRE#{ctx.inspire_id}. Date: {date}. " + f"Error: {e}." + ) + + +@dataclass(frozen=True) +class CopyrightMapper(MapperBase): + + id = "metadata.copyright" + + def map_value(self, src_metadata, ctx, logger): + """Transform copyrights.""" + # format: "© {holder} {year}, {statement} {url}" + copyrights = src_metadata.get("copyright", []) + result_list = [] + + for cp in copyrights: + holder = cp.get("holder", "") + statement = cp.get("statement", "") + url = cp.get("url", "") + year = str(cp.get("year", "")) + + if not any([holder, statement, url, year]): + return None + else: + parts = [] + if holder or year: + holder_year = " ".join(filter(None, [holder, year])) + parts.append(f"{holder_year}") + if statement or url: + statement_url = " ".join(filter(None, [statement, url])) + parts.append(statement_url) + rdm_copyright = "© " + ", ".join(parts) + + result_list.append(rdm_copyright) + return "
".join(result_list) + + +@dataclass(frozen=True) +class DescriptionMapper(MapperBase): + + id = "metadata.description" + + def map_value(self, src_metadata, ctx, logger): + """Mapping of abstracts.""" + abstracts = src_metadata.get("abstracts", []) + if abstracts: + return abstracts[0]["value"] + + +@dataclass(frozen=True) +class AdditionalDescriptionsMapper(MapperBase): + id = "metadata.additional_descriptions" + + def map_value(self, src_metadata, ctx, logger): + """Mapping of additional descriptions.""" + abstracts = src_metadata.get("abstracts", []) + additional_descriptions = [] + + if len(abstracts) > 1: + for x in abstracts[1:]: + additional_descriptions.append( + {"description": x["value"], "type": {"id": "abstract"}} + ) + + # TODO move it to book resource? + book_series = src_metadata.get("book_series", []) + for book in book_series: + book_title = book.get("title") + book_volume = book.get("volume") + if book_title: + additional_descriptions.append( + {"description": book_title, "type": {"id": "series-information"}} + ) + if book_volume: + additional_descriptions.append( + {"description": book_volume, "type": {"id": "series-information"}} + ) + + return additional_descriptions + + +@dataclass(frozen=True) +class SubjectsMapper(MapperBase): + id = "metadata.subjects" + + def map_value(self, src_metadata, ctx, logger): + """Mapping of keywords to subjects.""" + keywords = src_metadata.get("keywords", []) + mapped_subjects = [] + for keyword in keywords: + value = keyword.get("value") + if value: + mapped_subjects.append( + { + "subject": value, + } + ) + + return mapped_subjects + + +@dataclass(frozen=True) +class LanguagesMapper(MapperBase): + id = "metadata.languages" + + def map_value(self, src_metadata, ctx, logger): + """Mapping and converting of languages.""" + languages = src_metadata.get("languages", []) + mapped_langs = [] + for lang in languages: + try: + language = pycountry.languages.get(alpha_2=lang.lower()) + + if not language: + ctx.errors.append( + f"Language '{lang}' does not exist. INSPIRE#: {ctx.inspire_id}." + ) + return [] + mapped_langs.append({"id": language.alpha_3}) + except LookupError as e: + ctx.errors.append( + f"Failed mapping language '{lang}'. INSPIRE#: {ctx.inspire_id}. Error: {str(e)}." + ) + return mapped_langs \ No newline at end of file diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/contributors.py b/site/cds_rdm/inspire_harvester/transform/mappers/contributors.py new file mode 100644 index 00000000..c74a867c --- /dev/null +++ b/site/cds_rdm/inspire_harvester/transform/mappers/contributors.py @@ -0,0 +1,142 @@ +from dataclasses import dataclass + +from cds_rdm.inspire_harvester.transform.mappers.mapper import MapperBase + + +class CreatibutorsMapper(MapperBase): + + def _transform_author_identifiers(self, author): + """Transform ids of authors. Keeping only ORCID and CDS.""" + author_ids = author.get("ids", []) + processed_identifiers = [] + + schemes_map = { + "INSPIRE ID": "inspire_author", + "ORCID": "orcid", + } + + for author_id in author_ids: + author_scheme = author_id.get("schema") + if author_scheme in schemes_map.keys(): + processed_identifiers.append( + { + "identifier": author_id.get("value"), + "scheme": schemes_map[author_scheme], + } + ) + + return processed_identifiers + + def _transform_author_affiliations(self, author): + """Transform affiliations.""" + affiliations = author.get("affiliations", []) + mapped_affiliations = [] + + for affiliation in affiliations: + value = affiliation.get("value") + if value: + mapped_affiliations.append({"name": value}) + + return mapped_affiliations + + def _transform_creatibutors(self, authors, ctx): + """Transform creatibutors.""" + creatibutors = [] + try: + for author in authors: + first_name = author.get("first_name") + last_name = author.get("last_name") + full_name = author.get("full_name") + + rdm_creatibutor = { + "person_or_org": { + "type": "personal", + } + } + + if first_name: + rdm_creatibutor["person_or_org"]["given_name"] = first_name + if last_name: + rdm_creatibutor["person_or_org"]["family_name"] = last_name + else: + last_name, first_name = full_name.split(", ") + rdm_creatibutor["person_or_org"]["family_name"] = last_name + if first_name and last_name: + rdm_creatibutor["person_or_org"]["name"] = ( + last_name + ", " + first_name + ) + + creator_affiliations = self._transform_author_affiliations(author) + creator_identifiers = self._transform_author_identifiers(author) + role = author.get("inspire_roles") + + if creator_affiliations: + rdm_creatibutor["affiliations"] = creator_affiliations + + if creator_identifiers: + rdm_creatibutor["person_or_org"][ + "identifiers" + ] = creator_identifiers + + if role: + rdm_creatibutor["role"] = role[0] + creatibutors.append(rdm_creatibutor) + return creatibutors + except Exception as e: + ctx.errors.append( + f"Mapping authors field failed. INSPIRE#{ctx.inspire_id}. Error: {e}." + ) + return None + + def map_value(self, src, ctx, logger): + pass + + +creators_roles = ["author", "editor"] + + +@dataclass(frozen=True) +class AuthorsMapper(CreatibutorsMapper): + id = "metadata.creators" + + def map_value(self, src_metadata, ctx, logger): + authors = src_metadata.get("authors", []) + creators = [] + for author in authors: + inspire_roles = author.get("inspire_roles") + if not inspire_roles: + creators.append(author) + else: + for role in creators_roles: + if role in inspire_roles: + creators.append(author) + + corporate_authors = src_metadata.get("corporate_author", []) + mapped_corporate_authors = [] + for corporate_author in corporate_authors: + contributor = { + "person_or_org": { + "type": "organizational", + "name": corporate_author, + }, + } + mapped_corporate_authors.append(contributor) + + return self._transform_creatibutors(creators, ctx) + mapped_corporate_authors + + +@dataclass(frozen=True) +class ContributorsMapper(CreatibutorsMapper): + id = "metadata.contributors" + + def map_value(self, src_metadata, ctx, logger): + authors = src_metadata.get("authors", []) + contributors = [] + + for author in authors: + inspire_roles = author.get("inspire_roles", []) + for role in inspire_roles: + if role not in creators_roles: + contributors.append(author) + + return self._transform_creatibutors(contributors, ctx) diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/custom_fields.py b/site/cds_rdm/inspire_harvester/transform/mappers/custom_fields.py new file mode 100644 index 00000000..3ba70edd --- /dev/null +++ b/site/cds_rdm/inspire_harvester/transform/mappers/custom_fields.py @@ -0,0 +1,84 @@ +from dataclasses import dataclass + +from cds_rdm.inspire_harvester.transform.mappers.mapper import MapperBase +from cds_rdm.inspire_harvester.transform.utils import search_vocabulary +from idutils.normalizers import normalize_isbn + + +@dataclass(frozen=True) +class ImprintMapper(MapperBase): + + id = "custom_fields.imprint:imprint" + + def map_value(self, src_metadata, ctx, logger): + """Apply thesis field mapping.""" + imprint = src_metadata.get("imprints", []) + isbns = src_metadata.get("isbns", []) + + online_isbns = [] + for isbn in isbns: + value = isbn.get("value") + valid_isbn = normalize_isbn(value) + if not valid_isbn: + ctx.errors.append(f"Invalid ISBN '{value}'.") + else: + if isbn.get("medium") == "online": + online_isbns.append(valid_isbn) + + if len(online_isbns) > 1: + ctx.errors.append( + f"More than one electronic ISBN found: {online_isbns}." + ) + + place = imprint.get("place") if imprint else None + + # TODO this is true only for thesis + isbn = online_isbns[0] if online_isbns else None + out = {} + if place: + out["place"] = place + if isbn: + out["isbn"] = isbn + return out + + +@dataclass(frozen=True) +class CERNFieldsMapper(MapperBase): + """Map CERN specific custom fields""" + + id = "custom_fields" + + def map_value(self, src_metadata, ctx, logger): + """Apply mapping.""" + acc_exp_list = src_metadata.get("accelerator_experiments", []) + _accelerators = [] + _experiments = [] + for item in acc_exp_list: + accelerator = item.get("accelerator") + experiment = item.get("experiment") + institution = item.get("institution") + + if accelerator: + logger.debug(f"Searching vocabulary 'accelerator' for term: '{accelerator}'") + accelerator = f"{institution} {accelerator}" + result = search_vocabulary(accelerator, "accelerators", ctx, logger) + if result.total == 1: + logger.info(f"Found accelerator '{accelerator}'") + hit = list(result.hits)[0] + _accelerators.append({"id": hit["id"]}) + else: + logger.warning(f"Accelerator '{accelerator}' not found for INSPIRE#{ctx.inspire_id}") + + if experiment: + logger.debug( + f"Searching vocabulary 'experiments' for term: '{experiment}'") + result = search_vocabulary(experiment, "experiments", ctx, logger) + if result.total == 1: + logger.info(f"Found experiment '{experiment}'") + hit = list(result.hits)[0] + _experiments.append({"id": hit["id"]}) + else: + logger.warning( + f"Accelerator '{accelerator}' not found for INSPIRE#{ctx.inspire_id}") + + return {"cern:accelerators": _accelerators, "cern:experiments": _experiments} \ No newline at end of file diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/files.py b/site/cds_rdm/inspire_harvester/transform/mappers/files.py new file mode 100644 index 00000000..30cabe79 --- /dev/null +++ b/site/cds_rdm/inspire_harvester/transform/mappers/files.py @@ -0,0 +1,56 @@ +from dataclasses import dataclass + +from cds_rdm.inspire_harvester.transform.mappers.mapper import MapperBase + + +@dataclass(frozen=True) +class FilesMapper(MapperBase): + id = "files" + + def map_value(self, src_metadata, ctx, logger): + logger.debug(f"Starting _transform_files") + + rdm_files_entries = {} + inspire_files = src_metadata.get("documents", []) + logger.debug(f" Processing {len(inspire_files)} documents") + + for file in inspire_files: + logger.debug(f"Processing file: {file.get('filename', 'unknown')}") + filename = file["filename"] + if "pdf" not in filename: + # INSPIRE only exposes pdfs for us + filename = f"{filename}.pdf" + try: + file_details = { + "checksum": f"md5:{file['key']}", + "key": filename, + "access": {"hidden": False}, + "inspire_url": file["url"], # put this somewhere else + } + + rdm_files_entries[filename] = file_details + logger.info(f"File mapped: {file_details}. File name: {filename}.") + + file_metadata = {} + file_description = file.get("description") + file_original_url = file.get("original_url") + if file_description: + file_metadata["description"] = file_description + if file_original_url: + file_metadata["original_url"] = file_original_url + + if file_metadata: + rdm_files_entries[filename]["metadata"] = file_metadata + + except Exception as e: + ctx.errors.append( + f"Error occurred while mapping files. File key: {file['key']}. INSPIRE record id: {ctx.inspire_id}. Error: {e}." + ) + + logger.debug( + f"Files transformation completed with {len(ctx.errors)} errors" + ) + return { + "enabled": True, + "entries": rdm_files_entries, + } \ No newline at end of file diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/identifiers.py b/site/cds_rdm/inspire_harvester/transform/mappers/identifiers.py new file mode 100644 index 00000000..a1864cf9 --- /dev/null +++ b/site/cds_rdm/inspire_harvester/transform/mappers/identifiers.py @@ -0,0 +1,203 @@ +import json + +from dataclasses import dataclass +from flask import current_app + +from cds_rdm.inspire_harvester.transform.mappers.mapper import MapperBase +from idutils.normalizers import normalize_isbn +from idutils.validators import is_doi + + +@dataclass(frozen=True) +class DOIMapper(MapperBase): + id = "pids" + + def map_value(self, src_metadata, ctx, logger): + """Mapping of record dois.""" + DATACITE_PREFIX = current_app.config["DATACITE_PREFIX"] + dois = src_metadata.get("dois", []) + + if not dois: + return {} + + seen = set() + unique_dois = [] + for d in dois: + if d["value"] not in seen: + unique_dois.append(d) + seen.add(d["value"]) + + if len(unique_dois) > 1: + ctx.errors.append( + f"More than 1 DOI was found in INSPIRE#{ctx.inspire_id}." + ) + return None + elif len(unique_dois) == 0: + return None + else: + doi = unique_dois[0].get("value") + if is_doi(doi): + mapped_doi = { + "identifier": doi, + } + if doi.startswith(DATACITE_PREFIX): + mapped_doi["provider"] = "datacite" + else: + mapped_doi["provider"] = "external" + return {"doi": mapped_doi} + else: + ctx.errors.append( + f"DOI validation failed. DOI#{doi}. INSPIRE#{ctx.inspire_id}." + ) + return None + + +@dataclass(frozen=True) +class IdentifiersMapper(MapperBase): + id = "metadata.identifiers" + + def map_value(self, src_metadata, ctx, logger): + identifiers = [] + RDM_RECORDS_IDENTIFIERS_SCHEMES = current_app.config[ + "RDM_RECORDS_IDENTIFIERS_SCHEMES" + ] + RDM_RECORDS_RELATED_IDENTIFIERS_SCHEMES = current_app.config[ + "RDM_RECORDS_RELATED_IDENTIFIERS_SCHEMES" + ] + + external_sys_ids = src_metadata.get("external_system_identifiers", []) + + for external_sys_id in external_sys_ids: + schema = external_sys_id.get("schema").lower() + value = external_sys_id.get("value") + if schema == "cdsrdm": + schema = "cds" + if schema in RDM_RECORDS_IDENTIFIERS_SCHEMES.keys(): + identifiers.append({"identifier": value, "scheme": schema}) + elif schema in RDM_RECORDS_RELATED_IDENTIFIERS_SCHEMES.keys(): + continue + else: + ctx.errors.append( + f"Unexpected schema found in external_system_identifiers. Schema: {schema}, value: {value}. INSPIRE record id: {ctx.inspire_id}." + ) + unique_ids = [dict(t) for t in {tuple(sorted(d.items())) for d in identifiers}] + return unique_ids + + +@dataclass(frozen=True) +class RelatedIdentifiersMapper(MapperBase): + id = "metadata.related_identifiers" + + def map_value(self, src_metadata, ctx, logger): + """Mapping of alternate identifiers.""" + identifiers = [] + RDM_RECORDS_IDENTIFIERS_SCHEMES = current_app.config[ + "RDM_RECORDS_IDENTIFIERS_SCHEMES" + ] + RDM_RECORDS_RELATED_IDENTIFIERS_SCHEMES = current_app.config[ + "RDM_RECORDS_RELATED_IDENTIFIERS_SCHEMES" + ] + CDS_INSPIRE_IDS_SCHEMES_MAPPING = current_app.config[ + "CDS_INSPIRE_IDS_SCHEMES_MAPPING" + ] + + try: + # persistent_identifiers + persistent_ids = src_metadata.get("persistent_identifiers", []) + for persistent_id in persistent_ids: + schema = persistent_id.get("schema").lower() + schema = CDS_INSPIRE_IDS_SCHEMES_MAPPING.get(schema, schema) + value = persistent_id.get("value") + if schema in RDM_RECORDS_RELATED_IDENTIFIERS_SCHEMES.keys(): + new_id = { + "identifier": value, + "scheme": schema, + "relation_type": {"id": "isvariantformof"}, + "resource_type": {"id": "publication-other"}, + } + if schema == "doi": + new_id["relation_type"] = {"id": "isversionof"} + identifiers.append(new_id) + elif schema in RDM_RECORDS_IDENTIFIERS_SCHEMES.keys(): + continue + else: + ctx.errors.append( + f"Unexpected schema found in persistent_identifiers. Schema: {schema}, value: {value}. INSPIRE#: {ctx.inspire_id}." + ) + + # external_system_identifiers + external_sys_ids = src_metadata.get( + "external_system_identifiers", [] + ) + for external_sys_id in external_sys_ids: + schema = external_sys_id.get("schema").lower() + value = external_sys_id.get("value") + if schema == "cdsrdm": + continue + if schema in RDM_RECORDS_RELATED_IDENTIFIERS_SCHEMES.keys(): + new_id = { + "identifier": value, + "scheme": schema, + "relation_type": {"id": "isvariantformof"}, + "resource_type": {"id": "publication-other"}, + } + if schema == "doi": + new_id["relation_type"] = {"id": "isversionof"} + identifiers.append(new_id) + elif schema in RDM_RECORDS_IDENTIFIERS_SCHEMES.keys(): + continue + else: + ctx.errors.append( + f"Unexpected schema found in external_system_identifiers. Schema: {schema}, value: {value}. INSPIRE record id: {ctx.inspire_id}." + ) + + # ISBNs + isbns = src_metadata.get("isbns", []) + for isbn in isbns: + value = isbn.get("value") + _isbn = normalize_isbn(value) + if not _isbn: + ctx.errors.append(f"Invalid ISBN '{value}'.") + else: + identifiers.append( + { + "identifier": _isbn, + "scheme": "isbn", + "relation_type": {"id": "isvariantformof"}, + "resource_type": {"id": "publication-book"}, + } + ) + + arxiv_ids = src_metadata.get("arxiv_eprints", []) + for arxiv_id in arxiv_ids: + identifiers.append( + { + "scheme": "arxiv", + "identifier": f"arXiv:{arxiv_id["value"]}", + "relation_type": {"id": "isvariantformof"}, + "resource_type": {"id": "publication-other"}, + } + ) + + identifiers.append( + { + "scheme": "inspire", + "identifier": ctx.inspire_id, + "relation_type": {"id": "isvariantformof"}, + "resource_type": {"id": "publication-other"}, + } + ) + + seen = set() + unique_ids = [] + for d in identifiers: + s = json.dumps(d, sort_keys=True) + if s not in seen: + seen.add(s) + unique_ids.append(d) + return unique_ids + except Exception as e: + ctx.errors.append( + f"Failed mapping identifiers. INSPIRE#: {ctx.inspire_id}. Error: {e}." + ) + return None diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/mapper.py b/site/cds_rdm/inspire_harvester/transform/mappers/mapper.py new file mode 100644 index 00000000..0e4ba28d --- /dev/null +++ b/site/cds_rdm/inspire_harvester/transform/mappers/mapper.py @@ -0,0 +1,28 @@ +from abc import ABC, abstractmethod + +from cds_rdm.inspire_harvester.transform.utils import set_path + + +class MapperBase(ABC): + id: str + returns_patch: bool = False + + def apply(self, src_metadata, ctx, logger): + result = self.map_value(src_metadata, ctx, logger) + if not result: + return + if self.returns_patch: + if not isinstance(result, dict): + raise TypeError( + f"{self.__class__.__name__} returns_patch=True but returned " + f"{type(result).__name__}, expected dict" + ) + return result + + # Normal mode: wrap result under self.id + return set_path(self.id, result) + + @abstractmethod + def map_value(self, src, ctx, logger): + """Return a value (not a patch). Return None for no-op.""" + raise NotImplementedError diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/thesis.py b/site/cds_rdm/inspire_harvester/transform/mappers/thesis.py new file mode 100644 index 00000000..c22a8b4b --- /dev/null +++ b/site/cds_rdm/inspire_harvester/transform/mappers/thesis.py @@ -0,0 +1,47 @@ +from dataclasses import dataclass + +from babel_edtf import parse_edtf +from edtf.parser.grammar import ParseException +from .mapper import MapperBase + +@dataclass(frozen=True) +class ThesisPublicationDateMapper(MapperBase): + + id = "metadata.publication_date" + + def map_value(self, src_metadata, ctx, logger): + """Mapping of INSPIRE thesis_info.date to metadata.publication_date.""" + imprints = src_metadata.get("imprints", []) + imprint_date = imprints[0].get("date") if imprints else None + thesis_info = src_metadata.get("thesis_info", {}) + thesis_date = thesis_info.get("date") or ( + imprint_date if imprint_date else None + ) + + if thesis_date is None: + ctx.errors.append( + f"Thesis publication date transform failed. INSPIRE#{ctx.inspire_id}." + ) + return None + try: + parsed_date = str(parse_edtf(thesis_date)) + return parsed_date + except ParseException as e: + ctx.errors.append( + f"Publication date transformation failed." + f"INSPIRE#{ctx.inspire_id}. Date: {thesis_date}. " + f"Error: {e}." + ) + return None + + +@dataclass(frozen=True) +class ThesisDefenceDateMapper(MapperBase): + + id = "custom_fields.thesis:thesis.defense_date" + + def map_value(self, src_metadata, ctx, logger): + """Apply thesis field mapping.""" + thesis_info = src_metadata.get("thesis_info", {}) + defense_date = thesis_info.get("defense_date") + return defense_date \ No newline at end of file diff --git a/site/cds_rdm/inspire_harvester/transform/policies.py b/site/cds_rdm/inspire_harvester/transform/policies.py new file mode 100644 index 00000000..f74f7782 --- /dev/null +++ b/site/cds_rdm/inspire_harvester/transform/policies.py @@ -0,0 +1,41 @@ +from dataclasses import dataclass, field + +from typing import Dict, Tuple, List + +from cds_rdm.inspire_harvester.transform.resource_types import ResourceType +from cds_rdm.inspire_harvester.transform.mappers.mapper import MapperBase as Mapper + +@dataclass(frozen=True) +class MapperPolicy: + base: Tuple[Mapper, ...] + # per type: + add: Dict[ResourceType, Tuple[Mapper, ...]] = field(default_factory=dict) + replace: Dict[Tuple[ResourceType, str], Mapper] = field(default_factory=dict) + remove: Dict[ResourceType, Tuple[str, ...]] = field(default_factory=dict) + + def build_for(self, rt: ResourceType) -> List[Mapper]: + # start with base + mappers: List[Mapper] = list(self.base) + + # remove by id + remove_ids = set(self.remove.get(rt, ())) + mappers = [m for m in mappers if m.id not in remove_ids] + + # replace by (rt, mapper_id) + # replacement is done by id match + replacements = { + mid: mapper + for (rtype, mid), mapper in self.replace.items() + if rtype == rt + } + if replacements: + new_list = [] + for m in mappers: + new_list.append(replacements.get(m.id, m)) + mappers = new_list + + # add extra mappers for this type + mappers.extend(self.add.get(rt, ())) + + # optional: enforce stable ordering if needed + return mappers \ No newline at end of file diff --git a/site/cds_rdm/inspire_harvester/transform/resource_types.py b/site/cds_rdm/inspire_harvester/transform/resource_types.py new file mode 100644 index 00000000..4cf39bc6 --- /dev/null +++ b/site/cds_rdm/inspire_harvester/transform/resource_types.py @@ -0,0 +1,162 @@ +from enum import Enum + + +class ResourceType(str, Enum): + ARTICLE = "publication-article" + BOOK = "publication-book" + BOOK_CHAPTER = "publication-bookchapter" + CONFERENCE_PAPER = "publication-conferencepaper" + NOTE = "publication-technicalnote" + OTHER = "other" + PREPRINT = "publication-preprint" + PROCEEDINGS = "publication-conferenceproceedings" + REPORT = "publication-report" + THESIS = "publication-dissertation" + + +# Mapping from INSPIRE document types to CDS-RDM resource types +INSPIRE_DOCUMENT_TYPE_MAPPING = { + "article": ResourceType.ARTICLE, + "book": ResourceType.BOOK, + "report": ResourceType.REPORT, + "proceedings": ResourceType.PROCEEDINGS, + "book chapter": ResourceType.BOOK_CHAPTER, + "thesis": ResourceType.THESIS, + "note": ResourceType.NOTE, + "conference paper": ResourceType.CONFERENCE_PAPER, + "activity report": ResourceType.REPORT, +} + +class ResourceTypeDetector: + + def __init__(self, inspire_id, logger): + self.logger = logger + self.inspire_id = inspire_id + super().__init__() + + def _select_document_type(self, doc_types): + """Select document types.""" + + priority = { + v: i + for i, v in enumerate( + [ + "thesis", + "conference paper", + "article", + "book chapter", + "book", + "proceedings", + "report", + "activity report", + "note", + ] + ) + } + + # Select the candidate with the highest priority (lowest rank) + best_value = min(doc_types, key=lambda v: priority.get(v, float("inf"))) + return best_value + + def _check_if_published_art(self, src_metadata): + """Check if record is published article. + + follows https://github.com/inspirehep/inspire-schemas/blob/369a2f78189d9711cda8ac83e4e7d9344cc888da/inspire_schemas/readers/literature.py#L338 + """ + + def is_citeable(publication_info): + """Check fields to define if the article is citeable.""" + + def _item_has_pub_info(item): + return all(key in item for key in ("journal_title", "journal_volume")) + + def _item_has_page_or_artid(item): + return any(key in item for key in ("page_start", "artid")) + + has_pub_info = any(_item_has_pub_info(item) for item in publication_info) + has_page_or_artid = any( + _item_has_page_or_artid(item) for item in publication_info + ) + + return has_pub_info and has_page_or_artid + + pub_info = src_metadata.get("publication_info", []) + + citeable = pub_info and is_citeable(pub_info) + + submitted = "dois" in src_metadata and any( + "journal_title" in el for el in pub_info + ) + + return citeable or submitted + + def detect(self, src_metadata): + """Mapping of INSPIRE document type to resource type.""" + rt = None + errors = [] + document_types = src_metadata.get("document_type", []) + + self.logger.debug(f"Processing document types: {document_types}") + + if not document_types: + errors.append( + f"No document_type found in INSPIRE#{self.inspire_id}." + ) + return None + + # Check for multiple document types - fail for now + if len(document_types) > 1: + document_type = self._select_document_type(document_types) + self.logger.info( + f"Multiple document types found: {document_types}, mapped to {document_type}" + ) + else: + # Get the single document type + document_type = document_types[0] + + self.logger.debug(f"Document type found: {document_type}") + + # Use the reusable mapping + try: + rt = INSPIRE_DOCUMENT_TYPE_MAPPING[document_type] + except KeyError: + errors.append( + f"Error: Couldn't find resource type mapping rule for " + f"document_type '{document_type}'. INSPIRE#{self.inspire_id}. " + f"Available mappings: {list(INSPIRE_DOCUMENT_TYPE_MAPPING.keys())}" + ) + self.logger.error(f"Unmapped document type: {document_type}") + + if document_type == "article" and not self._check_if_published_art(src_metadata): + # preprint type does not exist in inspire, it is computed + rt = ResourceType.PREPRINT + + return rt, errors + + +# inspire enums https://github.com/inspirehep/inspire-schemas/blob/369a2f78189d9711cda8ac83e4e7d9344cc888da/inspire_schemas/records/elements + +# materials +# - addendum +# - additional material +# - data +# - editorial note +# - erratum +# - part +# - preprint +# - publication +# - reprint +# - software +# - translation +# - version + +# document types +# - activity report +# - article +# - book +# - book chapter +# - conference paper +# - note +# - proceedings +# - report +# - thesis diff --git a/site/cds_rdm/inspire_harvester/transform/transform_entry.py b/site/cds_rdm/inspire_harvester/transform/transform_entry.py new file mode 100644 index 00000000..e017d38e --- /dev/null +++ b/site/cds_rdm/inspire_harvester/transform/transform_entry.py @@ -0,0 +1,201 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2025 CERN. +# +# CDS-RDM is free software; you can redistribute it and/or modify it under +# the terms of the MIT License; see LICENSE file for more details. + +"""Transform RDM entry.""" +import json +from copy import deepcopy + +import pycountry +from babel_edtf import parse_edtf +from edtf.parser.grammar import ParseException +from flask import current_app + +from cds_rdm.inspire_harvester.transform.config import mapper_policy +from cds_rdm.inspire_harvester.transform.resource_types import \ + INSPIRE_DOCUMENT_TYPE_MAPPING, ResourceType, ResourceTypeDetector +from cds_rdm.inspire_harvester.transform.context import MetadataSerializationContext +from cds_rdm.inspire_harvester.transform.utils import deep_merge_all, assert_unique_ids +from idutils.normalizers import normalize_isbn +from idutils.validators import is_doi +from invenio_access.permissions import system_identity, system_user_id +from invenio_records_resources.proxies import current_service_registry +from opensearchpy import RequestError +from sqlalchemy.orm.exc import NoResultFound + +from cds_rdm.inspire_harvester.logger import Logger + + +class RDMEntry: + """Building of CDS-RDM entry record.""" + + def __init__(self, inspire_record): + """Initializes the RDM entry.""" + self.inspire_record = inspire_record + self.inspire_metadata = inspire_record["metadata"] + self.transformer = Inspire2RDM(self.inspire_record) + self.errors = [] + + def _id(self): + return self.inspire_record["id"] + + def _record(self): + """Transformation of metadata.""" + record = self.transformer.transform_record() + self.errors.extend(self.transformer.ctx.errors) + return record + + def _files(self): + """Transformation of files.""" + pass + + def _parent(self): + """Record parent minimal values.""" + parent = { + "access": { + "owned_by": { + "user": system_user_id, + } + } + } + return parent + + def _access(self): + """Record access minimal values.""" + access = { + "record": "public", + "files": "public", + } + return access + + def build(self): + """Perform building of CDS-RDM entry record.""" + inspire_id = self.inspire_record.get("id") + current_app.logger.debug( + f"[inspire_id={inspire_id}] Starting build of CDS-RDM entry record" + ) + + inspire_files = self.inspire_metadata.get("documents", []) + current_app.logger.debug( + f"[inspire_id={inspire_id}] Found {len(inspire_files)} files in INSPIRE record" + ) + + if not inspire_files: + current_app.logger.warning( + f"[inspire_id={inspire_id}] No files found in INSPIRE record - aborting transformation" + ) + self.errors.append( + f"INSPIRE record #{self.inspire_metadata['control_number']} has no files. Metadata-only records are not supported. Aborting record transformation." + ) + return None, self.errors + + current_app.logger.debug( + f"[inspire_id={inspire_id}] Starting record metadata transformation" + ) + record = self._record() + current_app.logger.debug( + f"[inspire_id={inspire_id}] Record metadata transformation completed" + ) + + rdm_record = { + "id": self._id(), + "metadata": record["metadata"], + "custom_fields": record["custom_fields"], + "files": record["files"], + "parent": self._parent(), + "access": self._access(), + } + + if record.get("pids"): + rdm_record["pids"] = record["pids"] + + current_app.logger.debug( + f"[inspire_id={inspire_id}] Building CDS-RDM entry record finished. RDM record: {rdm_record}." + ) + return rdm_record, self.errors + + +class Inspire2RDM: + """INSPIRE to CDS-RDM record mapping.""" + + def __init__(self, inspire_record, detector_cls=ResourceTypeDetector, + policy=mapper_policy): + """Initializes the Inspire2RDM class.""" + self.policy = policy + + self.inspire_record = inspire_record + self.inspire_original_metadata = inspire_record["metadata"] + self.inspire_id = self.inspire_record.get("id") + + self.logger = Logger(inspire_id=self.inspire_id) + rt, errors = detector_cls(self.inspire_id, + self.logger).detect(self.inspire_original_metadata) + self.ctx = MetadataSerializationContext(resource_type=rt, + inspire_id=self.inspire_id) + + for error in errors: + self.ctx.errors.append(error) + self.cds_id = self._get_cds_id(self.inspire_original_metadata) + + self.resource_type = rt + + # pre-clean data + self.inspire_metadata = self._clean_data(self.inspire_original_metadata) + + def _clean_data(self, src_metadata): + metadata = deepcopy(src_metadata) + self._clean_identifiers(metadata) + return metadata + + def _get_cds_id(self, src_metadata): + """Get CDS ID from INSPIRE metadata.""" + external_sys_ids = src_metadata.get("external_system_identifiers", []) + cds_id = None + for external_sys_id in external_sys_ids: + schema = external_sys_id.get("schema") + if schema.upper() in ["CDS", "CDSRDM"]: + cds_id = external_sys_id.get("value") + return cds_id + + def _clean_identifiers(self, metadata): + IDENTIFIERS_SCHEMES_TO_DROP = [ + "SPIRES", + "HAL", + "OSTI", + "SLAC", + "PROQUEST", + ] + external_sys_ids = metadata.get("external_system_identifiers", []) + persistent_ids = metadata.get("persistent_identifiers", []) + + cleaned_external_sys_ids = [] + cleaned_persistent_ids = [] + + for external_sys_id in external_sys_ids: + schema = external_sys_id.get("schema") + if schema.upper() not in IDENTIFIERS_SCHEMES_TO_DROP: + cleaned_external_sys_ids.append(external_sys_id) + for persistent_id in persistent_ids: + schema = persistent_id.get("schema") + + if schema.upper() not in IDENTIFIERS_SCHEMES_TO_DROP: + cleaned_persistent_ids.append(persistent_id) + + metadata["external_system_identifiers"] = cleaned_external_sys_ids + metadata["persistent_identifiers"] = cleaned_persistent_ids + + def transform_record(self): + """Perform record transformation.""" + self.logger.debug("Start transform_record") + + mappers = self.policy.build_for(self.resource_type) + assert_unique_ids(mappers) + patches = [m.apply(self.inspire_metadata, self.ctx, self.logger) for m in + mappers] + + out_record = deep_merge_all(patches) + return out_record + diff --git a/site/cds_rdm/inspire_harvester/transform/utils.py b/site/cds_rdm/inspire_harvester/transform/utils.py new file mode 100644 index 00000000..8c7ff72c --- /dev/null +++ b/site/cds_rdm/inspire_harvester/transform/utils.py @@ -0,0 +1,73 @@ +from collections import Counter + +from invenio_access.permissions import system_identity +from opensearchpy import RequestError +from sqlalchemy.exc import NoResultFound + +from invenio_records_resources.proxies import current_service_registry + +def assert_unique_ids(mappers): + ids = [m.id for m in mappers] + counts = Counter(ids) + dupes = [mid for mid, c in counts.items() if c > 1] + if dupes: + raise ValueError(f"Duplicate mapper ids in pipeline: {dupes}") + + +def set_path(path, value): + """Build nested dict from dotted path.""" + keys = path.split(".") + d = {} + cur = d + for k in keys[:-1]: + cur[k] = {} + cur = cur[k] + cur[keys[-1]] = value + return d + + +def deep_merge(a, b): + """Merge b into a (non-destructive) and return new dict.""" + out = dict(a) + for k, v in b.items(): + if ( + k in out + and isinstance(out[k], dict) + and isinstance(v, dict) + ): + out[k] = deep_merge(out[k], v) + else: + out[k] = v + return out + + +def deep_merge_all(parts): + out = {} + for p in parts: + if p is not None: + out = deep_merge(out, p) + return out + + +def search_vocabulary(term, vocab_type, ctx, logger): + """Search vocabulary utility function.""" + + + service = current_service_registry.get("vocabularies") + if "/" in term: + # escape the slashes + term = f'"{term}"' + try: + vocabulary_result = service.search( + system_identity, type=vocab_type, q=f'id:"{term}"' + ) + return vocabulary_result + except RequestError as e: + logger.error( + f"Failed vocabulary search ['{term}'] in '{vocab_type}'. INSPIRE#: {ctx.inspire_id}. Error: {e}." + ) + except NoResultFound as e: + logger.error( + f"Vocabulary term ['{term}'] not found in '{vocab_type}'. INSPIRE#: {ctx.inspire_id}" + ) + raise e diff --git a/site/cds_rdm/inspire_harvester/transform_entry.py b/site/cds_rdm/inspire_harvester/transform_entry.py deleted file mode 100644 index a5b2c14c..00000000 --- a/site/cds_rdm/inspire_harvester/transform_entry.py +++ /dev/null @@ -1,1173 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2025 CERN. -# -# CDS-RDM is free software; you can redistribute it and/or modify it under -# the terms of the MIT License; see LICENSE file for more details. - -"""Transform RDM entry.""" -import json -from copy import deepcopy - -import pycountry -from babel_edtf import parse_edtf -from edtf.parser.grammar import ParseException -from flask import current_app -from idutils.normalizers import normalize_isbn -from idutils.validators import is_doi -from invenio_access.permissions import system_identity, system_user_id -from invenio_records_resources.proxies import current_service_registry -from opensearchpy import RequestError -from sqlalchemy.orm.exc import NoResultFound - -from cds_rdm.inspire_harvester.logger import Logger - -# Mapping from INSPIRE document types to CDS-RDM resource types -INSPIRE_DOCUMENT_TYPE_MAPPING = { - "article": "publication-article", - "book": "publication-book", - "report": "publication-report", - "proceedings": "publication-conferenceproceeding", - "book chapter": "publication-section", - "thesis": "publication-dissertation", - "note": "publication-technicalnote", - "conference paper": "publication-conferencepaper", - "activity report": "publication-report", -} - - -class RDMEntry: - """Building of CDS-RDM entry record.""" - - def __init__(self, inspire_record): - """Initializes the RDM entry.""" - self.inspire_record = inspire_record - self.inspire_metadata = inspire_record["metadata"] - self.transformer = Inspire2RDM(self.inspire_record) - self.errors = [] - - def _id(self): - return self.inspire_record["id"] - - def _record(self): - """Transformation of metadata.""" - record, errors = self.transformer.transform_record() - self.errors.extend(errors) - return record - - def _files(self): - """Transformation of files.""" - files, errors = self.transformer.transform_files() - self.errors.extend(errors) - return files - - def _parent(self): - """Record parent minimal values.""" - parent = { - "access": { - "owned_by": { - "user": system_user_id, - } - } - } - return parent - - def _access(self): - """Record access minimal values.""" - access = { - "record": "public", - "files": "public", - } - return access - - def build(self): - """Perform building of CDS-RDM entry record.""" - inspire_id = self.inspire_record.get("id") - current_app.logger.debug( - f"[inspire_id={inspire_id}] Starting build of CDS-RDM entry record" - ) - - inspire_files = self.inspire_metadata.get("documents", []) - current_app.logger.debug( - f"[inspire_id={inspire_id}] Found {len(inspire_files)} files in INSPIRE record" - ) - - if not inspire_files: - current_app.logger.warning( - f"[inspire_id={inspire_id}] No files found in INSPIRE record - aborting transformation" - ) - self.errors.append( - f"INSPIRE record #{self.inspire_metadata['control_number']} has no files. Metadata-only records are not supported. Aborting record transformation." - ) - return None, self.errors - - current_app.logger.debug( - f"[inspire_id={inspire_id}] Starting record metadata transformation" - ) - record = self._record() - current_app.logger.debug( - f"[inspire_id={inspire_id}] Record metadata transformation completed" - ) - - current_app.logger.debug( - f"[inspire_id={inspire_id}] Starting files transformation" - ) - files = self._files() - current_app.logger.debug( - f"[inspire_id={inspire_id}] Files transformation completed" - ) - - rdm_record = { - "id": self._id(), - "metadata": record["metadata"], - "custom_fields": record["custom_fields"], - "files": files, - "parent": self._parent(), - "access": self._access(), - } - - if record.get("pids"): - rdm_record["pids"] = record["pids"] - - current_app.logger.debug( - f"[inspire_id={inspire_id}] Building CDS-RDM entry record finished. RDM record: {rdm_record}." - ) - return rdm_record, self.errors - - -class Inspire2RDM: - """INSPIRE to CDS-RDM record mapping.""" - - def __init__(self, inspire_record): - """Initializes the Inspire2RDM class.""" - self.inspire_record = inspire_record - self.inspire_original_metadata = inspire_record["metadata"] - self.inspire_metadata = deepcopy(inspire_record["metadata"]) - self.inspire_id = self.inspire_record.get("id") - self.logger = Logger(inspire_id=self.inspire_id) - self.metadata_errors = [] - self.files_errors = [] - - self._get_cds_id() - self._clean_data() - - # pre-clean data - - def _clean_data(self): - self._clean_identifiers() - - def _get_cds_id(self): - """Get CDS ID from INSPIRE metadata.""" - external_sys_ids = self.inspire_metadata.get("external_system_identifiers", []) - for external_sys_id in external_sys_ids: - schema = external_sys_id.get("schema") - if schema.upper() in ["CDS", "CDSRDM"]: - self.cds_id = external_sys_id.get("value") - - def _clean_identifiers(self): - IDENTIFIERS_SCHEMES_TO_DROP = [ - "SPIRES", - "HAL", - "OSTI", - "SLAC", - "PROQUEST", - ] - external_sys_ids = self.inspire_metadata.get("external_system_identifiers", []) - persistent_ids = self.inspire_metadata.get("persistent_identifiers", []) - - cleaned_external_sys_ids = [] - cleaned_persistent_ids = [] - - for external_sys_id in external_sys_ids: - schema = external_sys_id.get("schema") - if schema.upper() not in IDENTIFIERS_SCHEMES_TO_DROP: - cleaned_external_sys_ids.append(external_sys_id) - for persistent_id in persistent_ids: - schema = persistent_id.get("schema") - - if schema.upper() not in IDENTIFIERS_SCHEMES_TO_DROP: - cleaned_persistent_ids.append(persistent_id) - - self.inspire_metadata["external_system_identifiers"] = cleaned_external_sys_ids - self.inspire_metadata["persistent_identifiers"] = cleaned_persistent_ids - - def _transform_titles(self): - """Mapping of INSPIRE titles to metadata.title and additional_titles.""" - inspire_titles = self.inspire_metadata.get("titles", []) - rdm_title = None - rdm_additional_titles = [] - - for i, inspire_title in enumerate(inspire_titles): - try: - if i == 0: - rdm_title = inspire_title.get("title") - else: - alt_title = { - "title": inspire_title.get("title"), - "type": { - "id": "alternative-title", - }, - } - rdm_additional_titles.append(alt_title) - if inspire_title.get("subtitle"): - subtitle = { - "title": inspire_title.get("subtitle"), - "type": { - "id": "subtitle", - }, - } - rdm_additional_titles.append(subtitle) - except Exception as e: - self.metadata_errors.append( - f"Title {inspire_title} transform failed. INSPIRE#{self.inspire_id}. Error: {e}." - ) - return None, None - - return rdm_title, rdm_additional_titles - - def _validate_imprint(self): - """Validate that record has only 1 imprint.""" - imprints = self.inspire_metadata.get("imprints", []) - - if not imprints: - return - if len(imprints) > 1: - self.metadata_errors.append( - f"More than 1 imprint found. INSPIRE#{self.inspire_id}." - ) - return - - return imprints[0] - - def _transform_publisher(self): - """Mapping of publisher.""" - imprint = self._validate_imprint() - DATACITE_PREFIX = current_app.config["DATACITE_PREFIX"] - dois = self.inspire_metadata.get("dois", []) - - has_cds_doi = next( - (d["value"] for d in dois if d["value"].startswith(DATACITE_PREFIX)), - False, - ) - if has_cds_doi and not imprint.get("publisher"): - return "CERN" - elif not imprint: - return - return imprint.get("publisher") - - def _transform_publication_date(self): - """Mapping of INSPIRE thesis_info.date to metadata.publication_date.""" - imprint = self._validate_imprint() - - thesis_info = self.inspire_metadata.get("thesis_info", {}) - thesis_date = thesis_info.get("date") or ( - imprint.get("date") if imprint else None - ) - - if thesis_date is None: - self.metadata_errors.append( - f"Thesis publication date transform failed. INSPIRE#{self.inspire_id}." - ) - return None - try: - parsed_date = str(parse_edtf(thesis_date)) - return parsed_date - except ParseException as e: - self.metadata_errors.append( - f"Publication date transformation failed." - f"INSPIRE#{self.inspire_metadata['control_number']}. Date: {thesis_date}. " - f"Error: {e}." - ) - return None - - def _check_if_published_art(self): - """Check if record is published article. - - follows https://github.com/inspirehep/inspire-schemas/blob/369a2f78189d9711cda8ac83e4e7d9344cc888da/inspire_schemas/readers/literature.py#L338 - """ - - def is_citeable(publication_info): - """Check fields to define if the article is citeable.""" - - def _item_has_pub_info(item): - return all(key in item for key in ("journal_title", "journal_volume")) - - def _item_has_page_or_artid(item): - return any(key in item for key in ("page_start", "artid")) - - has_pub_info = any(_item_has_pub_info(item) for item in publication_info) - has_page_or_artid = any( - _item_has_page_or_artid(item) for item in publication_info - ) - - return has_pub_info and has_page_or_artid - - pub_info = self.inspire_original_metadata.get("publication_info", []) - - citeable = pub_info and is_citeable(pub_info) - - submitted = "dois" in self.inspire_original_metadata and any( - "journal_title" in el for el in pub_info - ) - - return citeable or submitted - - def _select_document_type(self, doc_types): - """Select document types.""" - - priority = { - v: i - for i, v in enumerate( - [ - "conference paper", - "thesis", - "article", - "proceedings", - "report", - "activity report", - "note", - ] - ) - } - - # Select the candidate with the highest priority (lowest rank) - best_value = min(doc_types, key=lambda v: priority.get(v, float("inf"))) - return best_value - - def _transform_document_type(self): - """Mapping of INSPIRE document type to resource type.""" - inspire_id = self.inspire_id - document_types = self.inspire_metadata.get("document_type", []) - - self.logger.debug(f"Processing document types: {document_types}") - - if not document_types: - self.metadata_errors.append( - f"No document_type found in INSPIRE#{inspire_id}." - ) - return None - - # Check for multiple document types - fail for now - if len(document_types) > 1: - document_type = self._select_document_type(document_types) - self.logger.info( - f"Multiple document types found: {document_types}, mapped to {document_type}" - ) - else: - # Get the single document type - document_type = document_types[0] - - self.logger.debug(f"Document type found: {document_type}") - - # Use the reusable mapping - if document_type not in INSPIRE_DOCUMENT_TYPE_MAPPING: - self.metadata_errors.append( - f"Error: Couldn't find resource type mapping rule for " - f"document_type '{document_type}'. INSPIRE#{inspire_id}. " - f"Available mappings: {list(INSPIRE_DOCUMENT_TYPE_MAPPING.keys())}" - ) - self.logger.error(f"Unmapped document type: {document_type}") - return None - - if document_type == "article" and not self._check_if_published_art(): - # preprint type does not exist in inspire, it is computed - mapped_resource_type = "publication-preprint" - - else: - mapped_resource_type = INSPIRE_DOCUMENT_TYPE_MAPPING[document_type] - self.logger.info( - f"Mapped document type '{document_type}' to resource type '{mapped_resource_type}'" - ) - - return {"id": mapped_resource_type} - - def _transform_contributors(self): - """Mapping of INSPIRE authors to contributors.""" - authors = self.inspire_metadata.get("authors", []) - contributors = [] - - for author in authors: - inspire_roles = author.get("inspire_roles", []) - if "supervisor" in inspire_roles: - contributors.append(author) - - return self._transform_creatibutors(contributors) - - def _transform_creators(self): - """Mapping of INSPIRE authors to creators.""" - authors = self.inspire_metadata.get("authors", []) - creators = [] - for author in authors: - inspire_roles = author.get("inspire_roles") - if not inspire_roles: - creators.append(author) - elif "author" in inspire_roles or "editor" in inspire_roles: - creators.append(author) - - corporate_authors = self.inspire_metadata.get("corporate_author", []) - mapped_corporate_authors = [] - for corporate_author in corporate_authors: - contributor = { - "person_or_org": { - "type": "organizational", - "name": corporate_author, - }, - } - mapped_corporate_authors.append(contributor) - - return self._transform_creatibutors(creators) + mapped_corporate_authors - - def _transform_creatibutors(self, authors): - """Transform creatibutors.""" - creatibutors = [] - try: - for author in authors: - first_name = author.get("first_name") - last_name = author.get("last_name") - full_name = author.get("full_name") - - rdm_creatibutor = { - "person_or_org": { - "type": "personal", - } - } - - if first_name: - rdm_creatibutor["person_or_org"]["given_name"] = first_name - if last_name: - rdm_creatibutor["person_or_org"]["family_name"] = last_name - else: - last_name, first_name = full_name.split(", ") - rdm_creatibutor["person_or_org"]["family_name"] = last_name - if first_name and last_name: - rdm_creatibutor["person_or_org"]["name"] = ( - last_name + ", " + first_name - ) - - creator_affiliations = self._transform_author_affiliations(author) - creator_identifiers = self._transform_author_identifiers(author) - role = author.get("inspire_roles") - - if creator_affiliations: - rdm_creatibutor["affiliations"] = creator_affiliations - - if creator_identifiers: - rdm_creatibutor["person_or_org"][ - "identifiers" - ] = creator_identifiers - - if role: - rdm_creatibutor["role"] = role[0] - creatibutors.append(rdm_creatibutor) - return creatibutors - except Exception as e: - self.metadata_errors.append( - f"Mapping authors field failed. INSPIRE#{self.inspire_id}. Error: {e}." - ) - return None - - def _transform_author_identifiers(self, author): - """Transform ids of authors. Keeping only ORCID and CDS.""" - author_ids = author.get("ids", []) - processed_identifiers = [] - - schemes_map = { - "INSPIRE ID": "inspire_author", - "ORCID": "orcid", - } - - for author_id in author_ids: - author_scheme = author_id.get("schema") - if author_scheme in schemes_map.keys(): - processed_identifiers.append( - { - "identifier": author_id.get("value"), - "scheme": schemes_map[author_scheme], - } - ) - - return processed_identifiers - - def _transform_author_affiliations(self, author): - """Transform affiliations.""" - affiliations = author.get("affiliations", []) - mapped_affiliations = [] - - for affiliation in affiliations: - value = affiliation.get("value") - if value: - mapped_affiliations.append({"name": value}) - - return mapped_affiliations - - def _transform_copyrights(self): - """Transform copyrights.""" - # format: "© {holder} {year}, {statement} {url}" - copyrights = self.inspire_metadata.get("copyright", []) - result_list = [] - - for cp in copyrights: - holder = cp.get("holder", "") - statement = cp.get("statement", "") - url = cp.get("url", "") - year = str(cp.get("year", "")) - - if not any([holder, statement, url, year]): - return None - else: - parts = [] - if holder or year: - holder_year = " ".join(filter(None, [holder, year])) - parts.append(f"{holder_year}") - if statement or url: - statement_url = " ".join(filter(None, [statement, url])) - parts.append(statement_url) - rdm_copyright = "© " + ", ".join(parts) - - result_list.append(rdm_copyright) - return "
".join(result_list) - - def _transform_dois(self): - """Mapping of record dois.""" - DATACITE_PREFIX = current_app.config["DATACITE_PREFIX"] - dois = self.inspire_metadata.get("dois", []) - - if not dois: - return - - seen = set() - unique_dois = [] - for d in dois: - if d["value"] not in seen: - unique_dois.append(d) - seen.add(d["value"]) - - if len(unique_dois) > 1: - self.metadata_errors.append( - f"More than 1 DOI was found in INSPIRE#{self.inspire_id}." - ) - return None - elif len(unique_dois) == 0: - return None - else: - doi = unique_dois[0].get("value") - if is_doi(doi): - mapped_doi = { - "identifier": doi, - } - if doi.startswith(DATACITE_PREFIX): - mapped_doi["provider"] = "datacite" - else: - mapped_doi["provider"] = "external" - return mapped_doi - else: - self.metadata_errors.append( - f"DOI validation failed. DOI#{doi}. INSPIRE#{self.inspire_id}." - ) - return None - - def _transform_identifiers(self): - identifiers = [] - RDM_RECORDS_IDENTIFIERS_SCHEMES = current_app.config[ - "RDM_RECORDS_IDENTIFIERS_SCHEMES" - ] - RDM_RECORDS_RELATED_IDENTIFIERS_SCHEMES = current_app.config[ - "RDM_RECORDS_RELATED_IDENTIFIERS_SCHEMES" - ] - - external_sys_ids = self.inspire_metadata.get("external_system_identifiers", []) - - for external_sys_id in external_sys_ids: - schema = external_sys_id.get("schema").lower() - value = external_sys_id.get("value") - if schema == "cdsrdm": - schema = "cds" - if schema in RDM_RECORDS_IDENTIFIERS_SCHEMES.keys(): - identifiers.append({"identifier": value, "scheme": schema}) - elif schema in RDM_RECORDS_RELATED_IDENTIFIERS_SCHEMES.keys(): - continue - else: - self.metadata_errors.append( - f"Unexpected schema found in external_system_identifiers. Schema: {schema}, value: {value}. INSPIRE record id: {self.inspire_id}." - ) - unique_ids = [dict(t) for t in {tuple(sorted(d.items())) for d in identifiers}] - return unique_ids - - def _transform_related_identifiers(self): - """Mapping of alternate identifiers.""" - identifiers = [] - RDM_RECORDS_IDENTIFIERS_SCHEMES = current_app.config[ - "RDM_RECORDS_IDENTIFIERS_SCHEMES" - ] - RDM_RECORDS_RELATED_IDENTIFIERS_SCHEMES = current_app.config[ - "RDM_RECORDS_RELATED_IDENTIFIERS_SCHEMES" - ] - CDS_INSPIRE_IDS_SCHEMES_MAPPING = current_app.config[ - "CDS_INSPIRE_IDS_SCHEMES_MAPPING" - ] - - try: - # persistent_identifiers - persistent_ids = self.inspire_metadata.get("persistent_identifiers", []) - for persistent_id in persistent_ids: - schema = persistent_id.get("schema").lower() - schema = CDS_INSPIRE_IDS_SCHEMES_MAPPING.get(schema, schema) - value = persistent_id.get("value") - if schema in RDM_RECORDS_RELATED_IDENTIFIERS_SCHEMES.keys(): - new_id = { - "identifier": value, - "scheme": schema, - "relation_type": {"id": "isvariantformof"}, - "resource_type": {"id": "publication-other"}, - } - if schema == "doi": - new_id["relation_type"] = {"id": "isversionof"} - identifiers.append(new_id) - elif schema in RDM_RECORDS_IDENTIFIERS_SCHEMES.keys(): - continue - else: - self.metadata_errors.append( - f"Unexpected schema found in persistent_identifiers. Schema: {schema}, value: {value}. INSPIRE#: {self.inspire_id}." - ) - - # external_system_identifiers - external_sys_ids = self.inspire_metadata.get( - "external_system_identifiers", [] - ) - for external_sys_id in external_sys_ids: - schema = external_sys_id.get("schema").lower() - value = external_sys_id.get("value") - if schema in RDM_RECORDS_RELATED_IDENTIFIERS_SCHEMES.keys(): - new_id = { - "identifier": value, - "scheme": schema, - "relation_type": {"id": "isvariantformof"}, - "resource_type": {"id": "publication-other"}, - } - if schema == "doi": - new_id["relation_type"] = {"id": "isversionof"} - identifiers.append(new_id) - elif schema in RDM_RECORDS_IDENTIFIERS_SCHEMES.keys(): - continue - else: - self.metadata_errors.append( - f"Unexpected schema found in external_system_identifiers. Schema: {schema}, value: {value}. INSPIRE record id: {self.inspire_id}." - ) - - # ISBNs - isbns = self.inspire_metadata.get("isbns", []) - for isbn in isbns: - value = isbn.get("value") - _isbn = normalize_isbn(value) - if not _isbn: - self.metadata_errors.append(f"Invalid ISBN '{value}'.") - else: - identifiers.append( - { - "identifier": _isbn, - "scheme": "isbn", - "relation_type": {"id": "isvariantformof"}, - "resource_type": {"id": "publication-book"}, - } - ) - - arxiv_ids = self.inspire_metadata.get("arxiv_eprints", []) - for arxiv_id in arxiv_ids: - identifiers.append( - { - "scheme": "arxiv", - "identifier": f"arXiv:{arxiv_id["value"]}", - "relation_type": {"id": "isvariantformof"}, - "resource_type": {"id": "publication-other"}, - } - ) - - identifiers.append( - { - "scheme": "inspire", - "identifier": self.inspire_id, - "relation_type": {"id": "isvariantformof"}, - "resource_type": {"id": "publication-other"}, - } - ) - - seen = set() - unique_ids = [] - for d in identifiers: - s = json.dumps(d, sort_keys=True) - if s not in seen: - seen.add(s) - unique_ids.append(d) - return unique_ids - except Exception as e: - self.metadata_errors.append( - f"Failed mapping identifiers. INSPIRE#: {self.inspire_id}. Error: {e}." - ) - return None - - def _transform_abstracts(self): - """Mapping of abstracts.""" - abstracts = self.inspire_metadata.get("abstracts", []) - if abstracts: - return abstracts[0]["value"] - return None - - def _transform_subjects(self): - """Mapping of keywords to subjects.""" - keywords = self.inspire_metadata.get("keywords", []) - mapped_subjects = [] - for keyword in keywords: - value = keyword.get("value") - if value: - mapped_subjects.append( - { - "subject": value, - } - ) - - return mapped_subjects - - def _transform_languages(self): - """Mapping and converting of languages.""" - languages = self.inspire_metadata.get("languages", []) - mapped_langs = [] - for lang in languages: - try: - language = pycountry.languages.get(alpha_2=lang.lower()) - - if not language: - self.metadata_errors.append( - f"Language '{lang}' does not exist. INSPIRE#: {self.inspire_id}." - ) - return - mapped_langs.append({"id": language.alpha_3}) - except LookupError as e: - self.metadata_errors.append( - f"Failed mapping language '{lang}'. INSPIRE#: {self.inspire_id}. Error: {str(e)}." - ) - return - return mapped_langs - - def _transform_additional_descriptions(self): - """Mapping of additional descriptions.""" - abstracts = self.inspire_metadata.get("abstracts", []) - additional_descriptions = [] - - if len(abstracts) > 1: - for x in abstracts[1:]: - additional_descriptions.append( - {"description": x["value"], "type": {"id": "abstract"}} - ) - - book_series = self.inspire_metadata.get("book_series", []) - for book in book_series: - book_title = book.get("title") - book_volume = book.get("volume") - if book_title: - additional_descriptions.append( - {"description": book_title, "type": {"id": "series-information"}} - ) - if book_volume: - additional_descriptions.append( - {"description": book_volume, "type": {"id": "series-information"}} - ) - - return additional_descriptions - - def _parse_cern_accelerator_experiment(self, value): - """Parse CERN-- format to extract accelerator and experiment.""" - self.logger.debug(f"Parsing CERN accelerator-experiment format: '{value}'") - - if not value or not value.startswith("CERN-"): - self.logger.debug( - f"Value '{value}' does not start with CERN- prefix, returning None" - ) - return None, None - - # Remove CERN- prefix and split on first dash - parts = value.replace("CERN-", "").strip().split("-", 1) - accelerator = parts[0] if parts else None - experiment = parts[1] if len(parts) > 1 else None - - self.logger.debug( - f"Parsed '{value}' into accelerator='{accelerator}', experiment='{experiment}'" - ) - return accelerator, experiment - - def _search_vocabulary(self, term, vocab_type): - """Search vocabulary utility function.""" - self.logger.debug(f"Searching vocabulary '{vocab_type}' for term: '{term}'") - - service = current_service_registry.get("vocabularies") - if "/" in term: - # escape the slashes - term = f'"{term}"' - try: - vocabulary_result = service.search( - system_identity, type=vocab_type, q=f'id:"{term}"' - ).to_dict() - hits = vocabulary_result.get("hits", {}).get("total", 0) - self.logger.debug( - f"Vocabulary search returned {hits} results for '{term}' in '{vocab_type}'" - ) - return vocabulary_result - except RequestError as e: - self.logger.error( - f"Failed vocabulary search ['{term}'] in '{vocab_type}'. INSPIRE#: {self.inspire_id}. Error: {e}." - ) - except NoResultFound as e: - self.logger.error( - f"Vocabulary term ['{term}'] not found in '{vocab_type}'. INSPIRE#: {self.inspire_id}" - ) - raise e - - def _transform_accelerators(self, inspire_accelerators): - """Map accelerators to CDS-RDM vocabulary.""" - inspire_id = self.inspire_metadata.get("control_number") - self.logger.debug( - f"Mapping {len(inspire_accelerators)} accelerators: {inspire_accelerators}" - ) - - mapped = [] - for accelerator in inspire_accelerators: - - if accelerator.startswith("CERN-"): - # First try the full string without CERN- prefix - full_string = accelerator.replace("CERN-", "").strip() - self.logger.debug( - f"CERN accelerator detected, trying full string '{full_string}' first" - ) - result = self._search_vocabulary(full_string, "accelerators") - - if result and result.get("hits", {}).get("total"): - # Found the full string as an accelerator - self.logger.info(f"Found accelerator '{full_string}'") - mapped.append({"id": result["hits"]["hits"][0]["id"]}) - continue - - # If not found, try parsing as CERN-- - self.logger.debug( - f"Term '{full_string}' not found, parse as combined format" - ) - parsed_acc, _ = self._parse_cern_accelerator_experiment(accelerator) - accelerator_to_search = parsed_acc if parsed_acc else full_string - - self.logger.debug( - f"Searching for accelerator: '{accelerator_to_search}'" - ) - - result = self._search_vocabulary(accelerator_to_search, "accelerators") - if result and result.get("hits", {}).get("total"): - self.logger.info( - f"Mapped accelerator '{accelerator_to_search}' from '{accelerator}'" - ) - mapped.append({"id": result["hits"]["hits"][0]["id"]}) - else: - self.logger.error( - f"Failed to map accelerator '{accelerator_to_search}'. INSPIRE#: {self.inspire_id}." - ) - return - else: - # Handle non-CERN accelerators - self.logger.debug(f"Processing non-CERN accelerator: '{accelerator}'") - result = self._search_vocabulary(accelerator, "accelerators") - if result and result.get("hits", {}).get("total"): - self.logger.info(f"Mapped non-CERN accelerator: '{accelerator}'") - mapped.append({"id": result["hits"]["hits"][0]["id"]}) - else: - self.logger.error( - f"Failed to map accelerator '{accelerator}'. INSPIRE#: {self.inspire_id}." - ) - return - - self.logger.debug( - f"Accelerator transformation completed, mapped {len(mapped)} accelerators" - ) - return mapped - - def _transform_experiments(self, inspire_experiments): - """Map experiments to CDS-RDM vocabulary.""" - self.logger.debug( - f"Start experiment transformation with {len(inspire_experiments)} experiments: {inspire_experiments}" - ) - - mapped = [] - for experiment in inspire_experiments: - self.logger.debug(f"Processing experiment: '{experiment}'") - - if experiment.startswith("CERN-"): - # First try the full string without CERN- prefix - full_string = experiment.replace("CERN-", "").strip() - self.logger.debug( - f"CERN experiment detected, trying full string '{full_string}' first" - ) - result = self._search_vocabulary(full_string, "experiments") - - if result and result.get("hits", {}).get("total"): - # Found the full string as an experiment - self.logger.info( - f"Found experiment '{full_string}' as full match in vocabulary" - ) - mapped.append({"id": result["hits"]["hits"][0]["id"]}) - continue - - # If not found, try parsing as CERN-- - self.logger.debug( - f"Full string '{full_string}' not found, attempting to parse as combined format" - ) - _, parsed_exp = self._parse_cern_accelerator_experiment(experiment) - experiment_to_search = parsed_exp if parsed_exp else full_string - - self.logger.debug( - f"Search for experiment component: '{experiment_to_search}'" - ) - - result = self._search_vocabulary(experiment_to_search, "experiments") - if result and result.get("hits", {}).get("total"): - self.logger.info( - f"Successfully mapped experiment '{experiment_to_search}' from '{experiment}'" - ) - mapped.append({"id": result["hits"]["hits"][0]["id"]}) - else: - self.logger.error( - f"Failed to map experiment '{experiment_to_search}'. INSPIRE#: {self.inspire_id}." - ) - return - else: - # Handle non-CERN experiments - self.logger.debug(f"Process non-CERN experiment: '{experiment}'") - result = self._search_vocabulary(experiment, "experiments") - if result and result.get("hits", {}).get("total"): - self.logger.info( - f"Successfully mapped non-CERN experiment: '{experiment}'" - ) - mapped.append({"id": result["hits"]["hits"][0]["id"]}) - else: - self.logger.error( - f"Failed to map experiment '{experiment}'. INSPIRE#: {self.inspire_id}." - ) - return - - self.logger.debug( - f"Experiment transformation completed, mapped {len(mapped)} experiments" - ) - return mapped - - def _transform_thesis(self, thesis_info): - """Transform thesis information to custom field format.""" - defense_date = thesis_info.get("defense_date") - return { - "date_defended": defense_date, - } - - def _transform_custom_fields(self): - """Mapping of custom fields.""" - custom_fields = {} - # TODO parse legacy name or check with Micha if they can expose name - inspire_accelerators = [] - inspire_experiments = [] - - # Extract accelerators and experiments, handling combined formats - for x in self.inspire_metadata.get("accelerator_experiments", []): - accelerator_value = x.get("accelerator") - experiment_value = x.get("legacy_name") - - # Handle accelerator field - if accelerator_value: - inspire_accelerators.append(accelerator_value) - - # Handle experiment field - if experiment_value: - inspire_experiments.append(experiment_value) - - thesis_info = self.inspire_metadata.get("thesis_info", {}) - defense_date = thesis_info.get("defense_date") - if defense_date: - custom_fields["thesis:thesis"] = self._transform_thesis(thesis_info) - - imprint = self._validate_imprint() - - # Map accelerator and experiment vocabularies - if mapped_accelerators := self._transform_accelerators(inspire_accelerators): - custom_fields["cern:accelerators"] = mapped_accelerators - - if mapped_experiments := self._transform_experiments(inspire_experiments): - custom_fields["cern:experiments"] = mapped_experiments - - # Map imprint place - if imprint_place := imprint.get("place") if imprint else None: - custom_fields["imprint:imprint"] = {"place": imprint_place} - - imprint_fields = custom_fields.get("imprint:imprint", {}) - - isbns = self.inspire_metadata.get("isbns", []) - online_isbns = [] - for isbn in isbns: - value = isbn.get("value") - valid_isbn = normalize_isbn(value) - if not valid_isbn: - self.metadata_errors.append(f"Invalid ISBN '{value}'.") - else: - if isbn.get("medium") == "online": - online_isbns.append(valid_isbn) - - if len(online_isbns) > 1: - self.metadata_errors.append( - f"More than one electronic ISBN found: {online_isbns}." - ) - elif len(online_isbns) == 1: - imprint_fields["isbn"] = online_isbns[0] - - if imprint_fields: - custom_fields["imprint:imprint"] = imprint_fields - return custom_fields - - def transform_metadata(self): - """Transform INSPIRE metadata.""" - self.logger.debug(f"Start transform_metadata") - - title, additional_titles = self._transform_titles() - - rdm_metadata = { - "creators": self._transform_creators(), - "contributor": self._transform_contributors(), - "identifiers": self._transform_identifiers(), - "related_identifiers": self._transform_related_identifiers(), - "publication_date": self._transform_publication_date(), - "languages": self._transform_languages(), - "publisher": self._transform_publisher(), - "title": title, - "additional_titles": additional_titles, - "copyright": self._transform_copyrights(), - "description": self._transform_abstracts(), - "additional_descriptions": self._transform_additional_descriptions(), - "subjects": self._transform_subjects(), - "resource_type": self._transform_document_type(), - } - - result = {k: v for k, v in rdm_metadata.items() if v} - self.logger.debug( - f"Metadata transformation completed with {len(result)} fields" - ) - - return result - - def transform_pids(self): - """Transform INSPIRE pids.""" - doi = self._transform_dois() - if doi: - pids = { - "doi": doi, - } - return pids - else: - return None - - def transform_record(self): - """Perform record transformation.""" - self.logger.debug("Start transform_record") - - metadata = self.transform_metadata() - - self.logger.debug("Transforming custom fields") - custom_fields = self._transform_custom_fields() - - record = { - "metadata": metadata, - "custom_fields": custom_fields, - } - self.logger.debug("Transforming PIDs") - pids = self.transform_pids() - - if pids: - record["pids"] = pids - self.logger.debug(f"PIDs added to record") - - self.logger.debug( - f"Record transformation completed with {len(self.metadata_errors)} errors" - ) - return record, self.metadata_errors - - def _transform_files(self): - """Mapping of INSPIRE documents to files.""" - self.logger.debug(f"Starting _transform_files") - - rdm_files_entries = {} - inspire_files = self.inspire_metadata.get("documents", []) - self.logger.debug(f" Processing {len(inspire_files)} documents") - - for file in inspire_files: - self.logger.debug(f"Processing file: {file.get('filename', 'unknown')}") - filename = file["filename"] - if "pdf" not in filename: - # INSPIRE only exposes pdfs for us - filename = f"{filename}.pdf" - try: - file_details = { - "checksum": f"md5:{file['key']}", - "key": filename, - "access": {"hidden": False}, - "inspire_url": file["url"], # put this somewhere else - } - - rdm_files_entries[filename] = file_details - self.logger.info(f"File mapped: {file_details}. File name: {filename}.") - - file_metadata = {} - file_description = file.get("description") - file_original_url = file.get("original_url") - if file_description: - file_metadata["description"] = file_description - if file_original_url: - file_metadata["original_url"] = file_original_url - - if file_metadata: - rdm_files_entries[filename]["metadata"] = file_metadata - - except Exception as e: - self.files_errors.append( - f"Error occurred while mapping files. File key: {file['key']}. INSPIRE record id: {self.inspire_id}. Error: {e}." - ) - - return { - "enabled": True, - "entries": rdm_files_entries, - } - - def transform_files(self): - """Transform INSPIRE documents and figures.""" - self.logger.debug("Starting transform_files") - - transformed_files = self._transform_files() - self.logger.debug( - f"Files transformation completed with {len(self.files_errors)} errors" - ) - return transformed_files, self.files_errors - - -# inspire enums https://github.com/inspirehep/inspire-schemas/blob/369a2f78189d9711cda8ac83e4e7d9344cc888da/inspire_schemas/records/elements - -# materials -# - addendum -# - additional material -# - data -# - editorial note -# - erratum -# - part -# - preprint -# - publication -# - reprint -# - software -# - translation -# - version - -# document types -# - activity report -# - article -# - book -# - book chapter -# - conference paper -# - note -# - proceedings -# - report -# - thesis diff --git a/site/cds_rdm/inspire_harvester/transformer.py b/site/cds_rdm/inspire_harvester/transformer.py index d921e883..5b0e911b 100644 --- a/site/cds_rdm/inspire_harvester/transformer.py +++ b/site/cds_rdm/inspire_harvester/transformer.py @@ -9,7 +9,7 @@ from flask import current_app from invenio_vocabularies.datastreams.transformers import BaseTransformer -from .transform_entry import RDMEntry +from .transform.transform_entry import RDMEntry class InspireJsonTransformer(BaseTransformer): From 01cebe01803c2b1a716f761f8080dc738802dc8a Mon Sep 17 00:00:00 2001 From: Karolina Przerwa Date: Mon, 12 Jan 2026 17:10:34 +0100 Subject: [PATCH 5/9] change(harvester): test new arch --- .../inspire_harvester/transform/__init__.py | 8 + .../inspire_harvester/transform/config.py | 100 +- .../inspire_harvester/transform/context.py | 16 +- .../transform/mappers/basic_metadata.py | 26 +- .../transform/mappers/contributors.py | 9 + .../transform/mappers/custom_fields.py | 35 +- .../transform/mappers/files.py | 15 +- .../transform/mappers/identifiers.py | 25 +- .../transform/mappers/thesis.py | 13 +- .../inspire_harvester/transform/policies.py | 24 +- .../transform/resource_types.py | 22 +- .../transform/transform_entry.py | 43 +- .../inspire_harvester/transform/utils.py | 20 +- site/cds_rdm/inspire_harvester/writer.py | 8 +- site/cds_rdm/schemes.py | 1 + site/tests/conftest.py | 2 +- .../data/completely_new_inspire_rec.json | 26 +- .../inspire_harvester/test_transformer.py | 1077 ++++++++--------- .../test_update_create_logic.py | 37 +- site/tests/utils.py | 2 +- 20 files changed, 795 insertions(+), 714 deletions(-) diff --git a/site/cds_rdm/inspire_harvester/transform/__init__.py b/site/cds_rdm/inspire_harvester/transform/__init__.py index e69de29b..30d07c8d 100644 --- a/site/cds_rdm/inspire_harvester/transform/__init__.py +++ b/site/cds_rdm/inspire_harvester/transform/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2026 CERN. +# +# CDS-RDM is free software; you can redistribute it and/or modify it under +# the terms of the GPL-2.0 License; see LICENSE file for more details. + +"""INSPIRE to CDS harvester module.""" diff --git a/site/cds_rdm/inspire_harvester/transform/config.py b/site/cds_rdm/inspire_harvester/transform/config.py index 3ad44e1f..7fb95d22 100644 --- a/site/cds_rdm/inspire_harvester/transform/config.py +++ b/site/cds_rdm/inspire_harvester/transform/config.py @@ -1,40 +1,65 @@ -from enum import Enum +# -*- coding: utf-8 -*- +# +# Copyright (C) 2026 CERN. +# +# CDS-RDM is free software; you can redistribute it and/or modify it under +# the terms of the GPL-2.0 License; see LICENSE file for more details. -from cds_rdm.inspire_harvester.transform.mappers.basic_metadata import \ - ResourceTypeMapper, TitleMapper, AdditionalTitlesMapper, PublisherMapper, \ - PublicationDateMapper, CopyrightMapper, DescriptionMapper, \ - AdditionalDescriptionsMapper, SubjectsMapper, LanguagesMapper -from cds_rdm.inspire_harvester.transform.mappers.contributors import AuthorsMapper, \ - ContributorsMapper -from cds_rdm.inspire_harvester.transform.mappers.custom_fields import ImprintMapper, \ - CERNFieldsMapper +"""INSPIRE to CDS harvester config module.""" + +from cds_rdm.inspire_harvester.transform.mappers.basic_metadata import ( + AdditionalDescriptionsMapper, + AdditionalTitlesMapper, + CopyrightMapper, + DescriptionMapper, + LanguagesMapper, + PublicationDateMapper, + PublisherMapper, + ResourceTypeMapper, + SubjectsMapper, + TitleMapper, +) +from cds_rdm.inspire_harvester.transform.mappers.contributors import ( + AuthorsMapper, + ContributorsMapper, +) +from cds_rdm.inspire_harvester.transform.mappers.custom_fields import ( + CERNFieldsMapper, + ImprintMapper, +) from cds_rdm.inspire_harvester.transform.mappers.files import FilesMapper -from cds_rdm.inspire_harvester.transform.mappers.identifiers import DOIMapper, \ - IdentifiersMapper, RelatedIdentifiersMapper -from cds_rdm.inspire_harvester.transform.mappers.thesis import ThesisDefenceDateMapper, \ - ThesisPublicationDateMapper +from cds_rdm.inspire_harvester.transform.mappers.identifiers import ( + DOIMapper, + IdentifiersMapper, + RelatedIdentifiersMapper, +) +from cds_rdm.inspire_harvester.transform.mappers.thesis import ( + ThesisDefenceDateMapper, + ThesisPublicationDateMapper, +) from cds_rdm.inspire_harvester.transform.policies import MapperPolicy from cds_rdm.inspire_harvester.transform.resource_types import ResourceType -BASE_MAPPERS = (FilesMapper(), - ResourceTypeMapper(), - DOIMapper(), - TitleMapper(), - AdditionalTitlesMapper(), - AuthorsMapper(), - ContributorsMapper(), - PublisherMapper(), - PublicationDateMapper(), - CopyrightMapper(), - DescriptionMapper(), - AdditionalDescriptionsMapper(), - SubjectsMapper(), - LanguagesMapper(), - ImprintMapper(), - CERNFieldsMapper(), - IdentifiersMapper(), - RelatedIdentifiersMapper() - ) +BASE_MAPPERS = ( + FilesMapper(), + ResourceTypeMapper(), + DOIMapper(), + TitleMapper(), + AdditionalTitlesMapper(), + AuthorsMapper(), + ContributorsMapper(), + PublisherMapper(), + PublicationDateMapper(), + CopyrightMapper(), + DescriptionMapper(), + AdditionalDescriptionsMapper(), + SubjectsMapper(), + LanguagesMapper(), + ImprintMapper(), + CERNFieldsMapper(), + IdentifiersMapper(), + RelatedIdentifiersMapper(), +) THESIS_MAPPERS = (ThesisDefenceDateMapper(),) inspire_mapper_policy = MapperPolicy(base=BASE_MAPPERS) @@ -44,11 +69,12 @@ add={ ResourceType.THESIS: THESIS_MAPPERS, }, + # if you had a generic mapper in base, you'd replace it here replace={ - (ResourceType.THESIS, "metadata.publication_date"): - ThesisPublicationDateMapper(), - # (ResourceType.ARTICLE, "metadata.publication_info"): ArticlePublicationInfoMapper(), - # (ResourceType.CONFERENCE_PAPER, "metadata.publication_info"): ConferencePublicationInfoMapper(), + ( + ResourceType.THESIS, + "metadata.publication_date", + ): ThesisPublicationDateMapper(), }, - # if you had a generic publication_info mapper in base, you'd replace it here + ) diff --git a/site/cds_rdm/inspire_harvester/transform/context.py b/site/cds_rdm/inspire_harvester/transform/context.py index 2b043332..a5747756 100644 --- a/site/cds_rdm/inspire_harvester/transform/context.py +++ b/site/cds_rdm/inspire_harvester/transform/context.py @@ -1,5 +1,13 @@ -from dataclasses import dataclass, field +# -*- coding: utf-8 -*- +# +# Copyright (C) 2026 CERN. +# +# CDS-RDM is free software; you can redistribute it and/or modify it under +# the terms of the GPL-2.0 License; see LICENSE file for more details. + +"""INSPIRE to CDS harvester context module.""" +from dataclasses import dataclass, field from typing import List from cds_rdm.inspire_harvester.transform.resource_types import ResourceType @@ -10,9 +18,3 @@ class MetadataSerializationContext: resource_type: ResourceType inspire_id: str errors: List[str] = field(default_factory=list) - - # def __init__(self, resource_type, inspire_id): - # self.resource_type = resource_type - # self.inspire_id = inspire_id - # self.logger = Logger(inspire_id=self.inspire_id) - diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/basic_metadata.py b/site/cds_rdm/inspire_harvester/transform/mappers/basic_metadata.py index f42476b4..6957bd01 100644 --- a/site/cds_rdm/inspire_harvester/transform/mappers/basic_metadata.py +++ b/site/cds_rdm/inspire_harvester/transform/mappers/basic_metadata.py @@ -1,8 +1,18 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2026 CERN. +# +# CDS-RDM is free software; you can redistribute it and/or modify it under +# the terms of the GPL-2.0 License; see LICENSE file for more details. + +"""INSPIRE to CDS harvester module.""" + +from dataclasses import dataclass + import pycountry from babel_edtf import parse_edtf -from dataclasses import dataclass -from flask import current_app from edtf.parser.grammar import ParseException +from flask import current_app from cds_rdm.inspire_harvester.transform.mappers.mapper import MapperBase @@ -57,7 +67,6 @@ def map_value(self, src_metadata, ctx, logger): return rdm_additional_titles - @dataclass(frozen=True) class PublisherMapper(MapperBase): id = "metadata.publisher" @@ -66,9 +75,7 @@ def validate(self, src, ctx): imprints = src.get("imprints", []) if len(imprints) > 1: - ctx.errors.append( - f"More than 1 imprint found. INSPIRE#{ctx.inspire_id}." - ) + ctx.errors.append(f"More than 1 imprint found. INSPIRE#{ctx.inspire_id}.") def map_value(self, src_metadata, ctx, logger): imprints = src_metadata.get("imprints", []) @@ -96,14 +103,17 @@ class PublicationDateMapper(MapperBase): id = "metadata.publication_date" def map_value(self, src_metadata, ctx, logger): + """Transform publication date.""" imprints = src_metadata.get("imprints", []) - imprint_date = imprints[0].get("date") + imprint_date = imprints[0].get("date") if imprints else None publication_date = src_metadata.get("publication_info", {}).get("year") creation_date = src_metadata.get("created") date = publication_date or imprint_date or creation_date + if date and isinstance(date, int): + date = str(date) try: parsed_date = str(parse_edtf(date)) return parsed_date @@ -234,4 +244,4 @@ def map_value(self, src_metadata, ctx, logger): ctx.errors.append( f"Failed mapping language '{lang}'. INSPIRE#: {ctx.inspire_id}. Error: {str(e)}." ) - return mapped_langs \ No newline at end of file + return mapped_langs diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/contributors.py b/site/cds_rdm/inspire_harvester/transform/mappers/contributors.py index c74a867c..12f8d13b 100644 --- a/site/cds_rdm/inspire_harvester/transform/mappers/contributors.py +++ b/site/cds_rdm/inspire_harvester/transform/mappers/contributors.py @@ -1,3 +1,12 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2026 CERN. +# +# CDS-RDM is free software; you can redistribute it and/or modify it under +# the terms of the GPL-2.0 License; see LICENSE file for more details. + +"""INSPIRE to CDS harvester module.""" + from dataclasses import dataclass from cds_rdm.inspire_harvester.transform.mappers.mapper import MapperBase diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/custom_fields.py b/site/cds_rdm/inspire_harvester/transform/mappers/custom_fields.py index 3ba70edd..e8fddf80 100644 --- a/site/cds_rdm/inspire_harvester/transform/mappers/custom_fields.py +++ b/site/cds_rdm/inspire_harvester/transform/mappers/custom_fields.py @@ -1,8 +1,18 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2026 CERN. +# +# CDS-RDM is free software; you can redistribute it and/or modify it under +# the terms of the GPL-2.0 License; see LICENSE file for more details. + +"""INSPIRE to CDS harvester module.""" + from dataclasses import dataclass +from idutils.normalizers import normalize_isbn + from cds_rdm.inspire_harvester.transform.mappers.mapper import MapperBase from cds_rdm.inspire_harvester.transform.utils import search_vocabulary -from idutils.normalizers import normalize_isbn @dataclass(frozen=True) @@ -12,7 +22,8 @@ class ImprintMapper(MapperBase): def map_value(self, src_metadata, ctx, logger): """Apply thesis field mapping.""" - imprint = src_metadata.get("imprints", []) + imprints = src_metadata.get("imprints", []) + imprint = imprints[0] if imprints else None isbns = src_metadata.get("isbns", []) online_isbns = [] @@ -26,9 +37,7 @@ def map_value(self, src_metadata, ctx, logger): online_isbns.append(valid_isbn) if len(online_isbns) > 1: - ctx.errors.append( - f"More than one electronic ISBN found: {online_isbns}." - ) + ctx.errors.append(f"More than one electronic ISBN found: {online_isbns}.") place = imprint.get("place") if imprint else None @@ -59,7 +68,9 @@ def map_value(self, src_metadata, ctx, logger): institution = item.get("institution") if accelerator: - logger.debug(f"Searching vocabulary 'accelerator' for term: '{accelerator}'") + logger.debug( + f"Searching vocabulary 'accelerator' for term: '{accelerator}'" + ) accelerator = f"{institution} {accelerator}" result = search_vocabulary(accelerator, "accelerators", ctx, logger) if result.total == 1: @@ -67,11 +78,14 @@ def map_value(self, src_metadata, ctx, logger): hit = list(result.hits)[0] _accelerators.append({"id": hit["id"]}) else: - logger.warning(f"Accelerator '{accelerator}' not found for INSPIRE#{ctx.inspire_id}") + logger.warning( + f"Accelerator '{accelerator}' not found for INSPIRE#{ctx.inspire_id}" + ) if experiment: logger.debug( - f"Searching vocabulary 'experiments' for term: '{experiment}'") + f"Searching vocabulary 'experiments' for term: '{experiment}'" + ) result = search_vocabulary(experiment, "experiments", ctx, logger) if result.total == 1: logger.info(f"Found experiment '{experiment}'") @@ -79,6 +93,7 @@ def map_value(self, src_metadata, ctx, logger): _experiments.append({"id": hit["id"]}) else: logger.warning( - f"Accelerator '{accelerator}' not found for INSPIRE#{ctx.inspire_id}") + f"Accelerator '{accelerator}' not found for INSPIRE#{ctx.inspire_id}" + ) - return {"cern:accelerators": _accelerators, "cern:experiments": _experiments} \ No newline at end of file + return {"cern:accelerators": _accelerators, "cern:experiments": _experiments} diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/files.py b/site/cds_rdm/inspire_harvester/transform/mappers/files.py index 30cabe79..0bbcc556 100644 --- a/site/cds_rdm/inspire_harvester/transform/mappers/files.py +++ b/site/cds_rdm/inspire_harvester/transform/mappers/files.py @@ -1,3 +1,12 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2026 CERN. +# +# CDS-RDM is free software; you can redistribute it and/or modify it under +# the terms of the GPL-2.0 License; see LICENSE file for more details. + +"""INSPIRE to CDS harvester module.""" + from dataclasses import dataclass from cds_rdm.inspire_harvester.transform.mappers.mapper import MapperBase @@ -47,10 +56,8 @@ def map_value(self, src_metadata, ctx, logger): f"Error occurred while mapping files. File key: {file['key']}. INSPIRE record id: {ctx.inspire_id}. Error: {e}." ) - logger.debug( - f"Files transformation completed with {len(ctx.errors)} errors" - ) + logger.debug(f"Files transformation completed with {len(ctx.errors)} errors") return { "enabled": True, "entries": rdm_files_entries, - } \ No newline at end of file + } diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/identifiers.py b/site/cds_rdm/inspire_harvester/transform/mappers/identifiers.py index a1864cf9..f0b0a616 100644 --- a/site/cds_rdm/inspire_harvester/transform/mappers/identifiers.py +++ b/site/cds_rdm/inspire_harvester/transform/mappers/identifiers.py @@ -1,12 +1,21 @@ -import json +# -*- coding: utf-8 -*- +# +# Copyright (C) 2026 CERN. +# +# CDS-RDM is free software; you can redistribute it and/or modify it under +# the terms of the GPL-2.0 License; see LICENSE file for more details. + +"""INSPIRE to CDS harvester module.""" +import json from dataclasses import dataclass -from flask import current_app -from cds_rdm.inspire_harvester.transform.mappers.mapper import MapperBase +from flask import current_app from idutils.normalizers import normalize_isbn from idutils.validators import is_doi +from cds_rdm.inspire_harvester.transform.mappers.mapper import MapperBase + @dataclass(frozen=True) class DOIMapper(MapperBase): @@ -28,9 +37,7 @@ def map_value(self, src_metadata, ctx, logger): seen.add(d["value"]) if len(unique_dois) > 1: - ctx.errors.append( - f"More than 1 DOI was found in INSPIRE#{ctx.inspire_id}." - ) + ctx.errors.append(f"More than 1 DOI was found in INSPIRE#{ctx.inspire_id}.") return None elif len(unique_dois) == 0: return None @@ -126,9 +133,7 @@ def map_value(self, src_metadata, ctx, logger): ) # external_system_identifiers - external_sys_ids = src_metadata.get( - "external_system_identifiers", [] - ) + external_sys_ids = src_metadata.get("external_system_identifiers", []) for external_sys_id in external_sys_ids: schema = external_sys_id.get("schema").lower() value = external_sys_id.get("value") @@ -173,7 +178,7 @@ def map_value(self, src_metadata, ctx, logger): identifiers.append( { "scheme": "arxiv", - "identifier": f"arXiv:{arxiv_id["value"]}", + "identifier": f"arXiv:{arxiv_id['value']}", "relation_type": {"id": "isvariantformof"}, "resource_type": {"id": "publication-other"}, } diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/thesis.py b/site/cds_rdm/inspire_harvester/transform/mappers/thesis.py index c22a8b4b..8154729f 100644 --- a/site/cds_rdm/inspire_harvester/transform/mappers/thesis.py +++ b/site/cds_rdm/inspire_harvester/transform/mappers/thesis.py @@ -1,9 +1,20 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2026 CERN. +# +# CDS-RDM is free software; you can redistribute it and/or modify it under +# the terms of the GPL-2.0 License; see LICENSE file for more details. + +"""INSPIRE to CDS harvester module.""" + from dataclasses import dataclass from babel_edtf import parse_edtf from edtf.parser.grammar import ParseException + from .mapper import MapperBase + @dataclass(frozen=True) class ThesisPublicationDateMapper(MapperBase): @@ -44,4 +55,4 @@ def map_value(self, src_metadata, ctx, logger): """Apply thesis field mapping.""" thesis_info = src_metadata.get("thesis_info", {}) defense_date = thesis_info.get("defense_date") - return defense_date \ No newline at end of file + return defense_date diff --git a/site/cds_rdm/inspire_harvester/transform/policies.py b/site/cds_rdm/inspire_harvester/transform/policies.py index f74f7782..26f75e2a 100644 --- a/site/cds_rdm/inspire_harvester/transform/policies.py +++ b/site/cds_rdm/inspire_harvester/transform/policies.py @@ -1,12 +1,23 @@ -from dataclasses import dataclass, field +# -*- coding: utf-8 -*- +# +# Copyright (C) 2026 CERN. +# +# CDS-RDM is free software; you can redistribute it and/or modify it under +# the terms of the GPL-2.0 License; see LICENSE file for more details. -from typing import Dict, Tuple, List +"""INSPIRE to CDS policies module.""" + +from dataclasses import dataclass, field +from typing import Dict, List, Tuple -from cds_rdm.inspire_harvester.transform.resource_types import ResourceType from cds_rdm.inspire_harvester.transform.mappers.mapper import MapperBase as Mapper +from cds_rdm.inspire_harvester.transform.resource_types import ResourceType + @dataclass(frozen=True) class MapperPolicy: + """Mapper policy class.""" + base: Tuple[Mapper, ...] # per type: add: Dict[ResourceType, Tuple[Mapper, ...]] = field(default_factory=dict) @@ -14,6 +25,7 @@ class MapperPolicy: remove: Dict[ResourceType, Tuple[str, ...]] = field(default_factory=dict) def build_for(self, rt: ResourceType) -> List[Mapper]: + """Build mapper for specified resource type.""" # start with base mappers: List[Mapper] = list(self.base) @@ -24,9 +36,7 @@ def build_for(self, rt: ResourceType) -> List[Mapper]: # replace by (rt, mapper_id) # replacement is done by id match replacements = { - mid: mapper - for (rtype, mid), mapper in self.replace.items() - if rtype == rt + mid: mapper for (rtype, mid), mapper in self.replace.items() if rtype == rt } if replacements: new_list = [] @@ -38,4 +48,4 @@ def build_for(self, rt: ResourceType) -> List[Mapper]: mappers.extend(self.add.get(rt, ())) # optional: enforce stable ordering if needed - return mappers \ No newline at end of file + return mappers diff --git a/site/cds_rdm/inspire_harvester/transform/resource_types.py b/site/cds_rdm/inspire_harvester/transform/resource_types.py index 4cf39bc6..f0656769 100644 --- a/site/cds_rdm/inspire_harvester/transform/resource_types.py +++ b/site/cds_rdm/inspire_harvester/transform/resource_types.py @@ -1,3 +1,12 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2026 CERN. +# +# CDS-RDM is free software; you can redistribute it and/or modify it under +# the terms of the GPL-2.0 License; see LICENSE file for more details. + +"""INSPIRE to CDS harvester module.""" + from enum import Enum @@ -27,9 +36,12 @@ class ResourceType(str, Enum): "activity report": ResourceType.REPORT, } + class ResourceTypeDetector: + """Resource type detector.""" def __init__(self, inspire_id, logger): + """Constructor.""" self.logger = logger self.inspire_id = inspire_id super().__init__() @@ -99,10 +111,8 @@ def detect(self, src_metadata): self.logger.debug(f"Processing document types: {document_types}") if not document_types: - errors.append( - f"No document_type found in INSPIRE#{self.inspire_id}." - ) - return None + errors.append(f"No document_type found in INSPIRE#{self.inspire_id}.") + return None, errors # Check for multiple document types - fail for now if len(document_types) > 1: @@ -127,7 +137,9 @@ def detect(self, src_metadata): ) self.logger.error(f"Unmapped document type: {document_type}") - if document_type == "article" and not self._check_if_published_art(src_metadata): + if document_type == "article" and not self._check_if_published_art( + src_metadata + ): # preprint type does not exist in inspire, it is computed rt = ResourceType.PREPRINT diff --git a/site/cds_rdm/inspire_harvester/transform/transform_entry.py b/site/cds_rdm/inspire_harvester/transform/transform_entry.py index e017d38e..7f9ca527 100644 --- a/site/cds_rdm/inspire_harvester/transform/transform_entry.py +++ b/site/cds_rdm/inspire_harvester/transform/transform_entry.py @@ -6,27 +6,18 @@ # the terms of the MIT License; see LICENSE file for more details. """Transform RDM entry.""" -import json from copy import deepcopy -import pycountry -from babel_edtf import parse_edtf -from edtf.parser.grammar import ParseException from flask import current_app +from invenio_access.permissions import system_user_id +from cds_rdm.inspire_harvester.logger import Logger from cds_rdm.inspire_harvester.transform.config import mapper_policy -from cds_rdm.inspire_harvester.transform.resource_types import \ - INSPIRE_DOCUMENT_TYPE_MAPPING, ResourceType, ResourceTypeDetector from cds_rdm.inspire_harvester.transform.context import MetadataSerializationContext -from cds_rdm.inspire_harvester.transform.utils import deep_merge_all, assert_unique_ids -from idutils.normalizers import normalize_isbn -from idutils.validators import is_doi -from invenio_access.permissions import system_identity, system_user_id -from invenio_records_resources.proxies import current_service_registry -from opensearchpy import RequestError -from sqlalchemy.orm.exc import NoResultFound - -from cds_rdm.inspire_harvester.logger import Logger +from cds_rdm.inspire_harvester.transform.resource_types import ( + ResourceTypeDetector, +) +from cds_rdm.inspire_harvester.transform.utils import assert_unique_ids, deep_merge_all class RDMEntry: @@ -121,8 +112,9 @@ def build(self): class Inspire2RDM: """INSPIRE to CDS-RDM record mapping.""" - def __init__(self, inspire_record, detector_cls=ResourceTypeDetector, - policy=mapper_policy): + def __init__( + self, inspire_record, detector_cls=ResourceTypeDetector, policy=mapper_policy + ): """Initializes the Inspire2RDM class.""" self.policy = policy @@ -131,10 +123,12 @@ def __init__(self, inspire_record, detector_cls=ResourceTypeDetector, self.inspire_id = self.inspire_record.get("id") self.logger = Logger(inspire_id=self.inspire_id) - rt, errors = detector_cls(self.inspire_id, - self.logger).detect(self.inspire_original_metadata) - self.ctx = MetadataSerializationContext(resource_type=rt, - inspire_id=self.inspire_id) + rt, errors = detector_cls(self.inspire_id, self.logger).detect( + self.inspire_original_metadata + ) + self.ctx = MetadataSerializationContext( + resource_type=rt, inspire_id=self.inspire_id + ) for error in errors: self.ctx.errors.append(error) @@ -146,6 +140,7 @@ def __init__(self, inspire_record, detector_cls=ResourceTypeDetector, self.inspire_metadata = self._clean_data(self.inspire_original_metadata) def _clean_data(self, src_metadata): + """Cleans the input data.""" metadata = deepcopy(src_metadata) self._clean_identifiers(metadata) return metadata @@ -193,9 +188,9 @@ def transform_record(self): mappers = self.policy.build_for(self.resource_type) assert_unique_ids(mappers) - patches = [m.apply(self.inspire_metadata, self.ctx, self.logger) for m in - mappers] + patches = [ + m.apply(self.inspire_metadata, self.ctx, self.logger) for m in mappers + ] out_record = deep_merge_all(patches) return out_record - diff --git a/site/cds_rdm/inspire_harvester/transform/utils.py b/site/cds_rdm/inspire_harvester/transform/utils.py index 8c7ff72c..7a124d0c 100644 --- a/site/cds_rdm/inspire_harvester/transform/utils.py +++ b/site/cds_rdm/inspire_harvester/transform/utils.py @@ -1,10 +1,19 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2026 CERN. +# +# CDS-RDM is free software; you can redistribute it and/or modify it under +# the terms of the GPL-2.0 License; see LICENSE file for more details. + +"""INSPIRE to CDS harvester module.""" + from collections import Counter from invenio_access.permissions import system_identity +from invenio_records_resources.proxies import current_service_registry from opensearchpy import RequestError from sqlalchemy.exc import NoResultFound -from invenio_records_resources.proxies import current_service_registry def assert_unique_ids(mappers): ids = [m.id for m in mappers] @@ -30,11 +39,7 @@ def deep_merge(a, b): """Merge b into a (non-destructive) and return new dict.""" out = dict(a) for k, v in b.items(): - if ( - k in out - and isinstance(out[k], dict) - and isinstance(v, dict) - ): + if k in out and isinstance(out[k], dict) and isinstance(v, dict): out[k] = deep_merge(out[k], v) else: out[k] = v @@ -49,10 +54,9 @@ def deep_merge_all(parts): return out -def search_vocabulary(term, vocab_type, ctx, logger): +def search_vocabulary(term, vocab_type, ctx, logger): """Search vocabulary utility function.""" - service = current_service_registry.get("vocabularies") if "/" in term: # escape the slashes diff --git a/site/cds_rdm/inspire_harvester/writer.py b/site/cds_rdm/inspire_harvester/writer.py index 4a649095..d3503f3b 100644 --- a/site/cds_rdm/inspire_harvester/writer.py +++ b/site/cds_rdm/inspire_harvester/writer.py @@ -213,12 +213,12 @@ def update_record( record.data["pids"].get("doi", {}).get("provider") == "datacite" ) - should_update_files = ( - existing_checksums != new_checksums - ) + should_update_files = existing_checksums != new_checksums should_create_new_version = ( - existing_checksums != new_checksums and existing_record_has_doi and existing_record_has_cds_doi + existing_checksums != new_checksums + and existing_record_has_doi + and existing_record_has_cds_doi ) files_enabled = record_dict.get("files", {}).get("enabled", False) diff --git a/site/cds_rdm/schemes.py b/site/cds_rdm/schemes.py index 2a131f1e..f6d27961 100644 --- a/site/cds_rdm/schemes.py +++ b/site/cds_rdm/schemes.py @@ -72,6 +72,7 @@ def is_indico(val): """ return str(val).isdigit() + def indico(): """Define scheme for Indico Links.""" return { diff --git a/site/tests/conftest.py b/site/tests/conftest.py index 8d90e60a..73c30bc9 100644 --- a/site/tests/conftest.py +++ b/site/tests/conftest.py @@ -261,7 +261,7 @@ def app_config(app_config, mock_datacite_client): label=_("Concept DOI"), ), ] - app_config["RDM_LOCK_EDIT_PUBLISHED_FILES"] = lock_edit_record_published_files + app_config["RDM_LOCK_EDIT_PUBLISHED_FILES"] = lock_edit_record_published_files return app_config diff --git a/site/tests/inspire_harvester/data/completely_new_inspire_rec.json b/site/tests/inspire_harvester/data/completely_new_inspire_rec.json index 93db4582..3be1fb6b 100644 --- a/site/tests/inspire_harvester/data/completely_new_inspire_rec.json +++ b/site/tests/inspire_harvester/data/completely_new_inspire_rec.json @@ -4,21 +4,19 @@ "hits": [ { "metadata": { - "publication_info": [ - { - "cnum": "C24-10-21.8", - "year": 2025, - "artid": "01165", - "journal_title": "EPJ Web Conf.", - "journal_record": { - "$ref": "https://inspirehep.net/api/journals/1211782" - }, - "journal_volume": "337", - "conference_record": { - "$ref": "https://inspirehep.net/api/conferences/2838772" - } + "publication_info": { + "cnum": "C24-10-21.8", + "year": 2025, + "artid": "01165", + "journal_title": "EPJ Web Conf.", + "journal_record": { + "$ref": "https://inspirehep.net/api/journals/1211782" + }, + "journal_volume": "337", + "conference_record": { + "$ref": "https://inspirehep.net/api/conferences/2838772" } - ], + }, "documents": [ { "key": "4550b6ee36afc3fdedc08d0423375ab4", diff --git a/site/tests/inspire_harvester/test_transformer.py b/site/tests/inspire_harvester/test_transformer.py index 3acdaefd..54a51a3f 100644 --- a/site/tests/inspire_harvester/test_transformer.py +++ b/site/tests/inspire_harvester/test_transformer.py @@ -10,35 +10,63 @@ from edtf.parser.grammar import ParseException -from cds_rdm.inspire_harvester.transform_entry import Inspire2RDM - - -@patch("cds_rdm.inspire_harvester.transform_entry.normalize_isbn") +from cds_rdm.inspire_harvester.logger import Logger +from cds_rdm.inspire_harvester.transform.context import MetadataSerializationContext +from cds_rdm.inspire_harvester.transform.mappers.basic_metadata import ( + AdditionalDescriptionsMapper, + AdditionalTitlesMapper, + CopyrightMapper, + DescriptionMapper, + LanguagesMapper, + PublicationDateMapper, + PublisherMapper, + ResourceTypeMapper, + SubjectsMapper, + TitleMapper, +) +from cds_rdm.inspire_harvester.transform.mappers.contributors import ( + AuthorsMapper, + ContributorsMapper, + CreatibutorsMapper, +) +from cds_rdm.inspire_harvester.transform.mappers.custom_fields import ImprintMapper +from cds_rdm.inspire_harvester.transform.mappers.files import FilesMapper +from cds_rdm.inspire_harvester.transform.mappers.identifiers import ( + DOIMapper, + IdentifiersMapper, + RelatedIdentifiersMapper, +) +from cds_rdm.inspire_harvester.transform.resource_types import ResourceType +from cds_rdm.inspire_harvester.transform.transform_entry import Inspire2RDM + + +@patch("cds_rdm.inspire_harvester.transform.mappers.identifiers.normalize_isbn") def test_transform_related_identifiers(mock_normalize_isbn, running_app): - """Test _transform_alternate_identifiers.""" + """Test RelatedIdentifiersMapper.""" mock_normalize_isbn.return_value = "978-0-123456-78-9" - inspire_record = { - "id": "12345", - "metadata": { - "persistent_identifiers": [ - {"schema": "arXiv", "value": "1234.5678"}, - {"schema": "URN", "value": "urn:nbn:de:hebis:77-25439"}, - {"schema": "ARK", "value": "ark_value"}, - ], - "external_system_identifiers": [{"schema": "CDS", "value": "2633876"}], - "isbns": [{"value": "978-0-123456-78-9"}], - "arxiv_eprints": [{"value": "1234.5678"}], - }, - } - transformer = Inspire2RDM(inspire_record) + src_metadata = { + "persistent_identifiers": [ + {"schema": "arXiv", "value": "1234.5678"}, + {"schema": "URN", "value": "urn:nbn:de:hebis:77-25439"}, + {"schema": "ARK", "value": "ark_value"}, + ], + "external_system_identifiers": [{"schema": "CDS", "value": "2633876"}], + "isbns": [{"value": "978-0-123456-78-9"}], + "arxiv_eprints": [{"value": "1234.5678"}], + } + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = RelatedIdentifiersMapper() - result = transformer._transform_related_identifiers() + result = mapper.map_value(src_metadata, ctx, logger) - # Should include arXiv, INSPIRE ID, and ISBN (CDS should be in indentifiers) + # Should include arXiv, INSPIRE ID, and ISBN (CDS should be in identifiers) assert len(result) == 6 assert { - "identifier": "1234.5678", + "identifier": "arXiv:1234.5678", "scheme": "arxiv", "relation_type": {"id": "isvariantformof"}, "resource_type": {"id": "publication-other"}, @@ -57,204 +85,216 @@ def test_transform_related_identifiers(mock_normalize_isbn, running_app): } in result -@patch("cds_rdm.inspire_harvester.transform_entry.normalize_isbn") -def test_transform_identifiers(mock_normalize_isbn, running_app): - """Test _transform_alternate_identifiers.""" - mock_normalize_isbn.return_value = "978-0-123456-78-9" - - inspire_record = { - "id": "12345", - "metadata": { - "persistent_identifiers": [{"schema": "arXiv", "value": "1234.5678"}], - "external_system_identifiers": [{"schema": "CDS", "value": "2633876"}], - "isbns": [{"value": "978-0-123456-78-9"}], - "arxiv_eprints": [{"value": "1234.5678"}], - }, +def test_transform_identifiers(running_app): + """Test IdentifiersMapper.""" + src_metadata = { + "persistent_identifiers": [{"schema": "arXiv", "value": "1234.5678"}], + "external_system_identifiers": [{"schema": "CDS", "value": "2633876"}], + "isbns": [{"value": "978-0-123456-78-9"}], + "arxiv_eprints": [{"value": "1234.5678"}], } - transformer = Inspire2RDM(inspire_record) + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = IdentifiersMapper() - result = transformer._transform_identifiers() + result = mapper.map_value(src_metadata, ctx, logger) assert len(result) == 1 assert {"identifier": "2633876", "scheme": "cds"} in result -@patch("cds_rdm.inspire_harvester.transform_entry.is_doi") +@patch("cds_rdm.inspire_harvester.transform.mappers.identifiers.is_doi") def test_transform_dois_valid_external(mock_is_doi, running_app): - """Test _transform_dois with valid DataCite DOI.""" + """Test DOIMapper with valid external DOI.""" mock_is_doi.return_value = True - inspire_record = { - "id": "12345", - "metadata": {"dois": [{"value": "10.5281/zenodo.12345"}]}, - } - transformer = Inspire2RDM(inspire_record) + src_metadata = {"dois": [{"value": "10.5281/zenodo.12345"}]} + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = DOIMapper() - result = transformer._transform_dois() + result = mapper.map_value(src_metadata, ctx, logger) - assert result["identifier"] == "10.5281/zenodo.12345" - assert result["provider"] == "external" + assert result["doi"]["identifier"] == "10.5281/zenodo.12345" + assert result["doi"]["provider"] == "external" -@patch("cds_rdm.inspire_harvester.transform_entry.is_doi") +@patch("cds_rdm.inspire_harvester.transform.mappers.identifiers.is_doi") def test_transform_dois_valid_datacite(mock_is_doi, running_app): - """Test _transform_dois with valid DataCite DOI.""" + """Test DOIMapper with valid DataCite DOI.""" mock_is_doi.return_value = True - inspire_record = { - "id": "12345", - "metadata": {"dois": [{"value": "10.17181/405kf-bmq61"}]}, - } - transformer = Inspire2RDM(inspire_record) + src_metadata = {"dois": [{"value": "10.17181/405kf-bmq61"}]} + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = DOIMapper() - result = transformer._transform_dois() + result = mapper.map_value(src_metadata, ctx, logger) - assert result["identifier"] == "10.17181/405kf-bmq61" - assert result["provider"] == "datacite" + assert result["doi"]["identifier"] == "10.17181/405kf-bmq61" + assert result["doi"]["provider"] == "datacite" -@patch("cds_rdm.inspire_harvester.transform_entry.is_doi") -def test_transform_dois_valid_external(mock_is_doi, running_app): - """Test _transform_dois with valid external DOI.""" +@patch("cds_rdm.inspire_harvester.transform.mappers.identifiers.is_doi") +def test_transform_dois_valid_external_second(mock_is_doi, running_app): + """Test DOIMapper with valid external DOI.""" mock_is_doi.return_value = True - inspire_record = { - "id": "12345", - "metadata": {"dois": [{"value": "10.1000/test"}]}, - } - transformer = Inspire2RDM(inspire_record) + src_metadata = {"dois": [{"value": "10.1000/test"}]} + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = DOIMapper() - result = transformer._transform_dois() + result = mapper.map_value(src_metadata, ctx, logger) - assert result["identifier"] == "10.1000/test" - assert result["provider"] == "external" + assert result["doi"]["identifier"] == "10.1000/test" + assert result["doi"]["provider"] == "external" -@patch("cds_rdm.inspire_harvester.transform_entry.is_doi") +@patch("cds_rdm.inspire_harvester.transform.mappers.identifiers.is_doi") def test_transform_dois_invalid(mock_is_doi, running_app): - """Test _transform_dois with invalid DOI.""" + """Test DOIMapper with invalid DOI.""" mock_is_doi.return_value = False - inspire_record = { - "id": "12345", - "metadata": {"dois": [{"value": "invalid_doi"}]}, - } - transformer = Inspire2RDM(inspire_record) + src_metadata = {"dois": [{"value": "invalid_doi"}]} + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = DOIMapper() - result = transformer._transform_dois() + result = mapper.map_value(src_metadata, ctx, logger) assert result is None - assert len(transformer.metadata_errors) == 1 - - -def test_transform_dois_multiple(): - """Test _transform_dois with multiple DOIs.""" - inspire_record = { - "id": "12345", - "metadata": { - "dois": [ - {"value": "10.1000/test1"}, - {"value": "10.1000/test2"}, - {"value": "10.1000/test1"}, - ] - }, + assert len(ctx.errors) == 1 + + +def test_transform_dois_multiple(running_app): + """Test DOIMapper with multiple DOIs.""" + src_metadata = { + "dois": [ + {"value": "10.1000/test1"}, + {"value": "10.1000/test2"}, + {"value": "10.1000/test1"}, + ] } - transformer = Inspire2RDM(inspire_record) + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = DOIMapper() - result = transformer._transform_dois() + result = mapper.map_value(src_metadata, ctx, logger) assert result is None - assert len(transformer.metadata_errors) == 1 - # assert result == [{"value": "10.1000/test1"}, {"value": "10.1000/test2"}] + assert len(ctx.errors) == 1 def test_transform_document_type_single(running_app): - """Test _transform_document_type with single type.""" - inspire_record = { - "id": "12345", - "metadata": {"control_number": 12345, "document_type": ["thesis"]}, - } - transformer = Inspire2RDM(inspire_record) + """Test ResourceTypeDetector with single type.""" + from cds_rdm.inspire_harvester.transform.resource_types import ResourceTypeDetector - result = transformer._transform_document_type() + src_metadata = {"control_number": 12345, "document_type": ["thesis"]} + logger = Logger(inspire_id="12345") + detector = ResourceTypeDetector(inspire_id="12345", logger=logger) - assert result == {"id": "publication-dissertation"} + result, errors = detector.detect(src_metadata) + + assert result == ResourceType.THESIS + assert len(errors) == 0 def test_transform_document_type_multiple(running_app): - """Test _transform_document_type with multiple types.""" - inspire_record = { - "id": "12345", - "metadata": { - "control_number": 12345, - "document_type": ["thesis", "article"], - }, + """Test ResourceTypeDetector with multiple types.""" + from cds_rdm.inspire_harvester.transform.resource_types import ResourceTypeDetector + + src_metadata = { + "control_number": 12345, + "document_type": ["thesis", "article"], } - transformer = Inspire2RDM(inspire_record) + logger = Logger(inspire_id="12345") + detector = ResourceTypeDetector(inspire_id="12345", logger=logger) - result = transformer._transform_document_type() + result, errors = detector.detect(src_metadata) - # found thesis - should take over - assert result == {"id": "publication-dissertation"} + # found thesis - should take over (highest priority) + assert result == ResourceType.THESIS + assert len(errors) == 0 def test_transform_document_type_unmapped(running_app): - """Test _transform_document_type with unmapped type.""" - inspire_record = { - "id": "12345", - "metadata": {"control_number": 12345, "document_type": ["unknown_type"]}, - } - transformer = Inspire2RDM(inspire_record) + """Test ResourceTypeDetector with unmapped type.""" + from cds_rdm.inspire_harvester.transform.resource_types import ResourceTypeDetector - result = transformer._transform_document_type() + src_metadata = {"control_number": 12345, "document_type": ["unknown_type"]} + logger = Logger(inspire_id="12345") + detector = ResourceTypeDetector(inspire_id="12345", logger=logger) + + result, errors = detector.detect(src_metadata) assert result is None - assert len(transformer.metadata_errors) == 1 - assert "Couldn't find resource type mapping" in transformer.metadata_errors[0] + assert len(errors) == 1 + assert "Couldn't find resource type mapping" in errors[0] def test_transform_document_type_none(running_app): - """Test _transform_document_type with no document types.""" - inspire_record = { - "id": "12345", - "metadata": {"control_number": 12345, "document_type": []}, - } - transformer = Inspire2RDM(inspire_record) + """Test ResourceTypeDetector with no document types.""" + from cds_rdm.inspire_harvester.transform.resource_types import ResourceTypeDetector + + src_metadata = {"control_number": 12345, "document_type": []} + logger = Logger(inspire_id="12345") + detector = ResourceTypeDetector(inspire_id="12345", logger=logger) - result = transformer._transform_document_type() + result, errors = detector.detect(src_metadata) assert result is None - assert len(transformer.metadata_errors) == 1 - assert "No document_type found" in transformer.metadata_errors[0] + assert len(errors) == 1 + assert "No document_type found" in errors[0] -def test_transform_titles_single_title(): - """Test _transform_titles with single title.""" - inspire_record = { - "id": "12345", - "metadata": {"titles": [{"title": "Main Title"}]}, - } - transformer = Inspire2RDM(inspire_record) +def test_transform_titles_single_title(running_app): + """Test TitleMapper and AdditionalTitlesMapper with single title.""" + src_metadata = {"titles": [{"title": "Main Title"}]} + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") - title, additional_titles = transformer._transform_titles() + title_mapper = TitleMapper() + title = title_mapper.map_value(src_metadata, ctx, logger) + + additional_titles_mapper = AdditionalTitlesMapper() + additional_titles = additional_titles_mapper.map_value(src_metadata, ctx, logger) assert title == "Main Title" assert additional_titles == [] -def test_transform_titles_multiple_titles_with_subtitle(): - """Test _transform_titles with multiple titles and subtitle.""" - inspire_record = { - "id": "12345", - "metadata": { - "titles": [ - {"title": "Main Title"}, - {"title": "Alternative Title"}, - {"title": "Title with Subtitle", "subtitle": "The Subtitle"}, - ] - }, +def test_transform_titles_multiple_titles_with_subtitle(running_app): + """Test TitleMapper and AdditionalTitlesMapper with multiple titles and subtitle.""" + src_metadata = { + "titles": [ + {"title": "Main Title"}, + {"title": "Alternative Title"}, + {"title": "Title with Subtitle", "subtitle": "The Subtitle"}, + ] } - transformer = Inspire2RDM(inspire_record) + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + + title_mapper = TitleMapper() + title = title_mapper.map_value(src_metadata, ctx, logger) - title, additional_titles = transformer._transform_titles() + additional_titles_mapper = AdditionalTitlesMapper() + additional_titles = additional_titles_mapper.map_value(src_metadata, ctx, logger) assert title == "Main Title" assert len(additional_titles) == 3 @@ -272,44 +312,30 @@ def test_transform_titles_multiple_titles_with_subtitle(): } in additional_titles -def test_transform_titles_exception(): - """Test _transform_titles with exception handling.""" - inspire_record = { - "id": "12345", - "metadata": {"titles": [None]}, # This will cause an exception - } - transformer = Inspire2RDM(inspire_record) - - title, additional_titles = transformer._transform_titles() - - assert title is None - assert additional_titles is None - assert len(transformer.metadata_errors) == 1 - - -def test_transform_creators(): - """Test _transform_creators.""" - inspire_record = { - "id": "12345", - "metadata": { - "authors": [ - { - "first_name": "John", - "last_name": "Doe", - "inspire_roles": ["author"], - }, - { - "first_name": "Jane", - "last_name": "Smith", - "inspire_roles": ["supervisor"], - }, - ], - "corporate_author": ["CERN", "NASA"], - }, - } - transformer = Inspire2RDM(inspire_record) +def test_transform_creators(running_app): + """Test AuthorsMapper.""" + src_metadata = { + "authors": [ + { + "first_name": "John", + "last_name": "Doe", + "inspire_roles": ["author"], + }, + { + "first_name": "Jane", + "last_name": "Smith", + "inspire_roles": ["supervisor"], + }, + ], + "corporate_author": ["CERN", "NASA"], + } + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = AuthorsMapper() - result = transformer._transform_creators() + result = mapper.map_value(src_metadata, ctx, logger) # Check corporate authors corporate_authors = [ @@ -320,36 +346,36 @@ def test_transform_creators(): assert corporate_authors[1]["person_or_org"]["name"] == "NASA" -def test_transform_contributors(): - """Test _transform_contributors.""" - inspire_record = { - "id": "12345", - "metadata": { - "authors": [ - { - "first_name": "John", - "last_name": "Doe", - "inspire_roles": ["author"], - }, - { - "first_name": "Jane", - "last_name": "Smith", - "inspire_roles": ["supervisor"], - }, - ], - }, - } - transformer = Inspire2RDM(inspire_record) - - result = transformer._transform_contributors() +def test_transform_contributors(running_app): + """Test ContributorsMapper.""" + src_metadata = { + "authors": [ + { + "first_name": "John", + "last_name": "Doe", + "inspire_roles": ["author"], + }, + { + "first_name": "Jane", + "last_name": "Smith", + "inspire_roles": ["supervisor"], + }, + ], + } + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = ContributorsMapper() - # Should include supervisors and corporate authors - assert len(result) == 1 # 1 supervisor (author went to creators) + result = mapper.map_value(src_metadata, ctx, logger) + # Should include supervisors (author went to creators) + assert len(result) == 1 # 1 supervisor -def test_transform_creatibutors(): - """Test _transform_creatibutors.""" +def test_transform_creatibutors(running_app): + """Test CreatibutorsMapper._transform_creatibutors.""" authors = [ { "first_name": "John", @@ -360,10 +386,12 @@ def test_transform_creatibutors(): } ] - inspire_record = {"id": "12345", "metadata": {}} - transformer = Inspire2RDM(inspire_record) + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + mapper = CreatibutorsMapper() - result = transformer._transform_creatibutors(authors) + result = mapper._transform_creatibutors(authors, ctx) assert len(result) == 1 author = result[0] @@ -377,8 +405,8 @@ def test_transform_creatibutors(): ] -def test_transform_author_identifiers(): - """Test _transform_author_identifiers.""" +def test_transform_author_identifiers(running_app): + """Test CreatibutorsMapper._transform_author_identifiers.""" author = { "ids": [ {"schema": "ORCID", "value": "0000-0000-0000-0000"}, @@ -387,18 +415,16 @@ def test_transform_author_identifiers(): ] } - inspire_record = {"id": "12345", "metadata": {}} - transformer = Inspire2RDM(inspire_record) - - result = transformer._transform_author_identifiers(author) + mapper = CreatibutorsMapper() + result = mapper._transform_author_identifiers(author) assert len(result) == 2 # Only ORCID and INSPIRE ID should be included assert {"identifier": "0000-0000-0000-0000", "scheme": "orcid"} in result assert {"identifier": "INSPIRE-12345", "scheme": "inspire_author"} in result -def test_transform_author_affiliations(): - """Test _transform_author_affiliations.""" +def test_transform_author_affiliations(running_app): + """Test CreatibutorsMapper._transform_author_affiliations.""" author = { "affiliations": [ {"value": "CERN"}, @@ -407,159 +433,178 @@ def test_transform_author_affiliations(): ] } - inspire_record = {"id": "12345", "metadata": {}} - transformer = Inspire2RDM(inspire_record) - - result = transformer._transform_author_affiliations(author) + mapper = CreatibutorsMapper() + result = mapper._transform_author_affiliations(author) assert len(result) == 2 assert {"name": "CERN"} in result assert {"name": "MIT"} in result -def test_transform_copyrights_complete(): - """Test _transform_copyrights with complete copyright info.""" - inspire_record = { - "id": "12345", - "metadata": { - "copyright": [ - { - "holder": "CERN", - "year": 2023, - "statement": "All rights reserved", - "url": "https://cern.ch", - } - ] - }, +def test_transform_copyrights_complete(running_app): + """Test CopyrightMapper with complete copyright info.""" + src_metadata = { + "copyright": [ + { + "holder": "CERN", + "year": 2023, + "statement": "All rights reserved", + "url": "https://cern.ch", + } + ] } - transformer = Inspire2RDM(inspire_record) + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = CopyrightMapper() - result = transformer._transform_copyrights() + result = mapper.map_value(src_metadata, ctx, logger) assert result == "© CERN 2023, All rights reserved https://cern.ch" -def test_transform_copyrights_multiple(): - """Test _transform_copyrights with multiple copyrights.""" - inspire_record = { - "id": "12345", - "metadata": { - "copyright": [ - {"holder": "CERN", "year": 2023}, - {"statement": "CC BY 4.0"}, - ] - }, +def test_transform_copyrights_multiple(running_app): + """Test CopyrightMapper with multiple copyrights.""" + src_metadata = { + "copyright": [ + {"holder": "CERN", "year": 2023}, + {"statement": "CC BY 4.0"}, + ] } - transformer = Inspire2RDM(inspire_record) + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = CopyrightMapper() - result = transformer._transform_copyrights() + result = mapper.map_value(src_metadata, ctx, logger) assert result == "© CERN 2023
© CC BY 4.0" -def test_transform_copyrights_empty(): - """Test _transform_copyrights with empty copyright.""" - inspire_record = {"id": "12345", "metadata": {"copyright": [{}]}} - transformer = Inspire2RDM(inspire_record) +def test_transform_copyrights_empty(running_app): + """Test CopyrightMapper with empty copyright.""" + src_metadata = {"copyright": [{}]} + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = CopyrightMapper() - result = transformer._transform_copyrights() + result = mapper.map_value(src_metadata, ctx, logger) assert result is None -def test_transform_abstracts(): - """Test _transform_abstracts.""" - inspire_record = { - "id": "12345", - "metadata": { - "abstracts": [ - {"value": "This is the main abstract"}, - {"value": "This is another abstract"}, - ] - }, +def test_transform_abstracts(running_app): + """Test DescriptionMapper.""" + src_metadata = { + "abstracts": [ + {"value": "This is the main abstract"}, + {"value": "This is another abstract"}, + ] } - transformer = Inspire2RDM(inspire_record) + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = DescriptionMapper() - result = transformer._transform_abstracts() + result = mapper.map_value(src_metadata, ctx, logger) assert result == "This is the main abstract" -def test_transform_abstracts_empty(): - """Test _transform_abstracts with no abstracts.""" - inspire_record = {"id": "12345", "metadata": {"abstracts": []}} - transformer = Inspire2RDM(inspire_record) +def test_transform_abstracts_empty(running_app): + """Test DescriptionMapper with no abstracts.""" + src_metadata = {"abstracts": []} + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = DescriptionMapper() - result = transformer._transform_abstracts() + result = mapper.map_value(src_metadata, ctx, logger) assert result is None -def test_transform_subjects(): - """Test _transform_subjects.""" - inspire_record = { - "id": "12345", - "metadata": { - "keywords": [ - {"value": "quantum mechanics"}, - {"value": "physics"}, - {}, # Empty keyword should be ignored - ] - }, +def test_transform_subjects(running_app): + """Test SubjectsMapper.""" + src_metadata = { + "keywords": [ + {"value": "quantum mechanics"}, + {"value": "physics"}, + {}, # Empty keyword should be ignored + ] } - transformer = Inspire2RDM(inspire_record) + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = SubjectsMapper() - result = transformer._transform_subjects() + result = mapper.map_value(src_metadata, ctx, logger) assert len(result) == 2 assert {"subject": "quantum mechanics"} in result assert {"subject": "physics"} in result -@patch("cds_rdm.inspire_harvester.transform_entry.pycountry") -def test_transform_languages(mock_pycountry): - """Test _transform_languages.""" +@patch("cds_rdm.inspire_harvester.transform.mappers.basic_metadata.pycountry") +def test_transform_languages(mock_pycountry, running_app): + """Test LanguagesMapper.""" mock_lang = Mock() mock_lang.alpha_3 = "eng" mock_pycountry.languages.get.return_value = mock_lang - inspire_record = {"id": "12345", "metadata": {"languages": ["en"]}} - transformer = Inspire2RDM(inspire_record) + src_metadata = {"languages": ["en"]} + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = LanguagesMapper() - result = transformer._transform_languages() + result = mapper.map_value(src_metadata, ctx, logger) assert result == [{"id": "eng"}] -@patch("cds_rdm.inspire_harvester.transform_entry.pycountry") -def test_transform_languages_invalid(mock_pycountry): - """Test _transform_languages with invalid language.""" +@patch("cds_rdm.inspire_harvester.transform.mappers.basic_metadata.pycountry") +def test_transform_languages_invalid(mock_pycountry, running_app): + """Test LanguagesMapper with invalid language.""" mock_pycountry.languages.get.return_value = None - inspire_record = {"id": "12345", "metadata": {"languages": ["invalid"]}} - transformer = Inspire2RDM(inspire_record) + src_metadata = {"languages": ["invalid"]} + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = LanguagesMapper() - result = transformer._transform_languages() + result = mapper.map_value(src_metadata, ctx, logger) - assert result is None - assert len(transformer.metadata_errors) == 1 - - -def test_transform_additional_descriptions(): - """Test _transform_additional_descriptions.""" - inspire_record = { - "id": "12345", - "metadata": { - "abstracts": [ - {"value": "Main abstract"}, - {"value": "Additional abstract"}, - ], - "book_series": [{"title": "Series Title", "volume": "Vol. 1"}], - }, + assert result == [] + assert len(ctx.errors) == 1 + + +def test_transform_additional_descriptions(running_app): + """Test AdditionalDescriptionsMapper.""" + src_metadata = { + "abstracts": [ + {"value": "Main abstract"}, + {"value": "Additional abstract"}, + ], + "book_series": [{"title": "Series Title", "volume": "Vol. 1"}], } - transformer = Inspire2RDM(inspire_record) + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = AdditionalDescriptionsMapper() - result = transformer._transform_additional_descriptions() + result = mapper.map_value(src_metadata, ctx, logger) assert len(result) == 3 assert { @@ -573,45 +618,26 @@ def test_transform_additional_descriptions(): assert {"description": "Vol. 1", "type": {"id": "series-information"}} in result -def test_parse_cern_accelerator_experiment(): - """Test _parse_cern_accelerator_experiment.""" - inspire_record = {"id": "12345", "metadata": {}} - transformer = Inspire2RDM(inspire_record) - - accelerator, experiment = transformer._parse_cern_accelerator_experiment( - "CERN-LHC-ATLAS" - ) - assert accelerator == "LHC" - assert experiment == "ATLAS" - - accelerator, experiment = transformer._parse_cern_accelerator_experiment("CERN-LEP") - assert accelerator == "LEP" - assert experiment is None - - accelerator, experiment = transformer._parse_cern_accelerator_experiment("NOT-CERN") - assert accelerator is None - assert experiment is None - - -def test_transform_files(): - """Test transform_files method.""" - inspire_record = { - "id": "12345", - "metadata": { - "documents": [ - { - "filename": "test", - "key": "abc123", - "url": "https://example.com/file", - "description": "Test file", - "original_url": "https://original.com/file", - } - ] - }, +def test_transform_files(running_app): + """Test FilesMapper.""" + src_metadata = { + "documents": [ + { + "filename": "test", + "key": "abc123", + "url": "https://example.com/file", + "description": "Test file", + "original_url": "https://original.com/file", + } + ] } - transformer = Inspire2RDM(inspire_record) + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = FilesMapper() - result, errors = transformer.transform_files() + result = mapper.map_value(src_metadata, ctx, logger) assert result["enabled"] is True assert "test.pdf" in result["entries"] @@ -624,132 +650,116 @@ def test_transform_files(): assert file_entry["metadata"]["original_url"] == "https://original.com/file" -def test_transform_no_files_error(): - """Test that error is present when no files are on the record.""" - inspire_record = { - "id": "12345", - "metadata": { - "control_number": 12345, - "documents": [], # No documents/files - "document_type": ["thesis"], - "titles": [{"title": "Test Title"}], - }, +def test_transform_no_files_error(running_app): + """Test FilesMapper with no files.""" + src_metadata = { + "control_number": 12345, + "documents": [], # No documents/files + "document_type": ["thesis"], + "titles": [{"title": "Test Title"}], } - transformer = Inspire2RDM(inspire_record) + ctx = MetadataSerializationContext( + resource_type=ResourceType.THESIS, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = FilesMapper() - result, errors = transformer.transform_files() + result = mapper.map_value(src_metadata, ctx, logger) assert result == {"enabled": True, "entries": {}} -def test_transform_imprint_place(): - """Test how inspire_record["metadata"]["imprints"][0]["place"] is transformed by Inspire2RDM class.""" - inspire_record = { - "id": "12345", - "metadata": { - "imprints": [{"place": "Geneva", "publisher": "CERN"}], - "control_number": 12345, - }, - } - transformer = Inspire2RDM(inspire_record) - - result = transformer._transform_custom_fields() - - assert "imprint:imprint" in result - assert result["imprint:imprint"]["place"] == "Geneva" - - -def test_transform_imprint_place_with_isbn(): - """Test imprint place transformation with ISBN.""" - inspire_record = { - "id": "12345", - "metadata": { - "imprints": [{"place": "New York", "publisher": "Springer"}], - "isbns": [{"value": "978-3-16-148410-0", "medium": "online"}], - "control_number": 12345, - }, +def test_transform_imprint_place(running_app): + """Test ImprintMapper.""" + src_metadata = { + "imprints": [{"place": "Geneva", "publisher": "CERN"}], + "control_number": 12345, } - transformer = Inspire2RDM(inspire_record) + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = ImprintMapper() - result = transformer._transform_custom_fields() + result = mapper.map_value(src_metadata, ctx, logger) - assert "imprint:imprint" in result - assert result["imprint:imprint"]["place"] == "New York" - assert result["imprint:imprint"]["isbn"] == "978-3-16-148410-0" + assert "place" in result + assert result["place"] == "Geneva" -def test_transform_imprint_place_no_imprints(): - """Test imprint place transformation when no imprints are present.""" - inspire_record = { - "id": "12345", - "metadata": { - "imprints": [], - "control_number": 12345, - }, +def test_transform_imprint_place_with_isbn(running_app): + """Test ImprintMapper with ISBN.""" + src_metadata = { + "imprints": [{"place": "New York", "publisher": "Springer"}], + "isbns": [{"value": "978-3-16-148410-0", "medium": "online"}], + "control_number": 12345, } - transformer = Inspire2RDM(inspire_record) + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = ImprintMapper() - result = transformer._transform_custom_fields() + result = mapper.map_value(src_metadata, ctx, logger) - assert "imprint:imprint" not in result + assert "place" in result + assert result["place"] == "New York" + assert result["isbn"] == "978-3-16-148410-0" -def test_transform_imprint_place_multiple_imprints(): - """Test imprint place transformation with multiple imprints (should generate error).""" - inspire_record = { - "id": "12345", - "metadata": { - "imprints": [ - {"place": "Geneva", "publisher": "CERN"}, - {"place": "New York", "publisher": "Springer"}, - ], - "control_number": 12345, - }, +def test_transform_imprint_place_no_imprints(running_app): + """Test ImprintMapper when no imprints are present.""" + src_metadata = { + "imprints": [], + "control_number": 12345, } - transformer = Inspire2RDM(inspire_record) + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = ImprintMapper() - result = transformer._transform_custom_fields() + result = mapper.map_value(src_metadata, ctx, logger) - assert "imprint:imprint" not in result - assert len(transformer.metadata_errors) == 1 - assert "More than 1 imprint found" in transformer.metadata_errors[0] + assert result == {} -def test_transform_files_figures_omitted(): +def test_transform_files_figures_omitted(running_app): """Test that figure type files are omitted from file transformation.""" - inspire_record = { - "id": "12345", - "metadata": { - "control_number": 12345, - "documents": [ - { - "filename": "thesis.pdf", - "key": "doc123", - "url": "https://example.com/thesis.pdf", - } - ], - "figures": [ - { - "filename": "figure1.pdf", - "key": "fig123", - "url": "https://example.com/figure1.pdf", - }, - { - "filename": "figure2.png", - "key": "fig456", - "url": "https://example.com/figure2.png", - }, - ], - }, - } - transformer = Inspire2RDM(inspire_record) + src_metadata = { + "control_number": 12345, + "documents": [ + { + "filename": "thesis.pdf", + "key": "doc123", + "url": "https://example.com/thesis.pdf", + } + ], + "figures": [ + { + "filename": "figure1.pdf", + "key": "fig123", + "url": "https://example.com/figure1.pdf", + }, + { + "filename": "figure2.png", + "key": "fig456", + "url": "https://example.com/figure2.png", + }, + ], + } + ctx = MetadataSerializationContext( + resource_type=ResourceType.THESIS, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = FilesMapper() - result, errors = transformer.transform_files() + result = mapper.map_value(src_metadata, ctx, logger) # Documents should be included assert "thesis.pdf" in result["entries"] - # Figures should be omitted/ignored + # Figures should be omitted/ignored (FilesMapper only processes documents) assert "figure1.pdf" not in result["entries"] assert "figure2.png" not in result["entries"] @@ -757,131 +767,76 @@ def test_transform_files_figures_omitted(): assert len(result["entries"]) == 1 -def test_transform_files_pdf_extension(): - """Test transform_files adds .pdf extension when missing.""" - inspire_record = { - "id": "12345", - "metadata": { - "documents": [ - { - "filename": "document.pdf", - "key": "abc123", - "url": "https://example.com/file", - } - ] - }, +def test_transform_files_pdf_extension(running_app): + """Test FilesMapper adds .pdf extension when missing.""" + src_metadata = { + "documents": [ + { + "filename": "document.pdf", + "key": "abc123", + "url": "https://example.com/file", + } + ] } - transformer = Inspire2RDM(inspire_record) + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = FilesMapper() - result, errors = transformer.transform_files() + result = mapper.map_value(src_metadata, ctx, logger) # Should not add .pdf extension if already present assert "document.pdf" in result["entries"] -def test_validate_imprint_single(): - """Test _validate_imprint with single imprint.""" - inspire_record = { - "id": "12345", - "metadata": {"imprints": [{"publisher": "Test Publisher"}]}, - } - transformer = Inspire2RDM(inspire_record) - - imprint = transformer._validate_imprint() - - assert imprint == {"publisher": "Test Publisher"} - - -def test_validate_imprint_multiple(): - """Test _validate_imprint with multiple imprints.""" - inspire_record = { - "id": "12345", - "metadata": { - "imprints": [{"publisher": "Publisher 1"}, {"publisher": "Publisher 2"}] - }, - } - transformer = Inspire2RDM(inspire_record) - - imprint = transformer._validate_imprint() - - assert imprint is None - assert len(transformer.metadata_errors) == 1 - assert "More than 1 imprint found" in transformer.metadata_errors[0] - - -def test_validate_imprint_none(): - """Test _validate_imprint with no imprints.""" - inspire_record = {"id": "12345", "metadata": {"imprints": []}} - transformer = Inspire2RDM(inspire_record) - - imprint = transformer._validate_imprint() - - assert imprint is None - - -def test_transform_publisher(): - """Test _transform_publisher.""" - inspire_record = { - "id": "12345", - "metadata": {"imprints": [{"publisher": "Test Publisher"}]}, - } - transformer = Inspire2RDM(inspire_record) +def test_transform_publisher(running_app): + """Test PublisherMapper.""" + src_metadata = {"imprints": [{"publisher": "Test Publisher"}]} + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = PublisherMapper() - publisher = transformer._transform_publisher() + publisher = mapper.map_value(src_metadata, ctx, logger) assert publisher == "Test Publisher" -@patch("cds_rdm.inspire_harvester.transform_entry.parse_edtf") -def test_transform_publication_date_from_thesis(mock_parse_edtf): - """Test _transform_publication_date from thesis_info.""" - mock_parse_edtf.return_value = "2023" - - inspire_record = {"id": "12345", "metadata": {"thesis_info": {"date": "2023"}}} - transformer = Inspire2RDM(inspire_record) - - date = transformer._transform_publication_date() - - assert date == "2023" - mock_parse_edtf.assert_called_once_with("2023") - - -@patch("cds_rdm.inspire_harvester.transform_entry.parse_edtf") -def test_transform_publication_date_from_imprint(mock_parse_edtf): - """Test _transform_publication_date from imprint.""" +@patch("cds_rdm.inspire_harvester.transform.mappers.basic_metadata.parse_edtf") +def test_transform_publication_date_from_imprint(mock_parse_edtf, running_app): + """Test PublicationDateMapper from imprint.""" mock_parse_edtf.return_value = "2023" - inspire_record = {"id": "12345", "metadata": {"imprints": [{"date": "2023"}]}} - transformer = Inspire2RDM(inspire_record) + src_metadata = {"imprints": [{"date": "2023"}]} + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = PublicationDateMapper() - date = transformer._transform_publication_date() + date = mapper.map_value(src_metadata, ctx, logger) assert date == "2023" -@patch("cds_rdm.inspire_harvester.transform_entry.parse_edtf") -def test_transform_publication_date_parse_exception(mock_parse_edtf): - """Test _transform_publication_date with parse exception.""" +@patch("cds_rdm.inspire_harvester.transform.mappers.basic_metadata.parse_edtf") +def test_transform_publication_date_parse_exception(mock_parse_edtf, running_app): + """Test PublicationDateMapper with parse exception.""" mock_parse_edtf.side_effect = ParseException("Invalid date") - inspire_record = { - "id": "12345", - "metadata": {"control_number": 12345, "thesis_info": {"date": "invalid"}}, + src_metadata = { + "control_number": 12345, + "imprints": [{"date": "invalid"}], } - transformer = Inspire2RDM(inspire_record) - - date = transformer._transform_publication_date() - - assert date is None - assert len(transformer.metadata_errors) == 1 - - -def test_transform_publication_date_no_date(): - """Test _transform_publication_date with no date available.""" - inspire_record = {"id": "12345", "metadata": {}} - transformer = Inspire2RDM(inspire_record) + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="12345" + ) + logger = Logger(inspire_id="12345") + mapper = PublicationDateMapper() - date = transformer._transform_publication_date() + date = mapper.map_value(src_metadata, ctx, logger) assert date is None - assert len(transformer.metadata_errors) == 1 + assert len(ctx.errors) == 1 diff --git a/site/tests/inspire_harvester/test_update_create_logic.py b/site/tests/inspire_harvester/test_update_create_logic.py index c9f8cd91..1bb0154a 100644 --- a/site/tests/inspire_harvester/test_update_create_logic.py +++ b/site/tests/inspire_harvester/test_update_create_logic.py @@ -165,11 +165,17 @@ def test_update_no_CDS_DOI_one_doc_type(running_app, location, scientific_commun def test_update_no_CDS_DOI_multiple_doc_types( - running_app, location, scientific_community, datastream_config, minimal_record_with_files + running_app, + location, + scientific_community, + datastream_config, + minimal_record_with_files, ): service = current_rdm_records_service - minimal_record_with_files["metadata"]["resource_type"] = {"id": "publication-preprint"} + minimal_record_with_files["metadata"]["resource_type"] = { + "id": "publication-preprint" + } minimal_record_with_files["metadata"]["related_identifiers"] = [ { "identifier": "2104.13342", @@ -197,11 +203,16 @@ def test_update_no_CDS_DOI_multiple_doc_types( record = current_rdm_records_service.read(system_identity, record["id"]) # from preprint to conference paper - assert record.data["metadata"]["resource_type"]["id"] == "publication-conferencepaper" + assert ( + record.data["metadata"]["resource_type"]["id"] == "publication-conferencepaper" + ) # ensure we didn't create a new version assert record._record.versions.latest_index == 1 # check title updated - assert record.data["metadata"]["title"] == "Search for pseudoscalar bosons decaying into $e^+e^-$ pairs in the NA64 experiment at the CERN SPS" + assert ( + record.data["metadata"]["title"] + == "Search for pseudoscalar bosons decaying into $e^+e^-$ pairs in the NA64 experiment at the CERN SPS" + ) # check files replaced # when we manage non-CDS record - we trust INSPIRE as a source of truth # therefore files will be synced 1:1 with INSPIRE @@ -216,8 +227,7 @@ def test_update_no_CDS_DOI_from_metadata_only_to_files( """Test update record, originally no files, adding files to the same version.""" service = current_rdm_records_service - minimal_record["metadata"]["resource_type"] = { - "id": "publication-preprint"} + minimal_record["metadata"]["resource_type"] = {"id": "publication-preprint"} minimal_record["metadata"]["related_identifiers"] = [ { "identifier": "2104.13345", @@ -232,8 +242,8 @@ def test_update_no_CDS_DOI_from_metadata_only_to_files( RDMRecord.index.refresh() with open( - "tests/inspire_harvester/data/record_with_no_cds_DOI_multiple_doc_type2.json", - "r", + "tests/inspire_harvester/data/record_with_no_cds_DOI_multiple_doc_type2.json", + "r", ) as f: new_record = json.load(f) @@ -244,13 +254,16 @@ def test_update_no_CDS_DOI_from_metadata_only_to_files( record = current_rdm_records_service.read(system_identity, record["id"]) # from preprint to conference paper - assert record.data["metadata"]["resource_type"][ - "id"] == "publication-conferencepaper" + assert ( + record.data["metadata"]["resource_type"]["id"] == "publication-conferencepaper" + ) # ensure we didn't create a new version assert record._record.versions.latest_index == 1 # check title updated - assert record.data["metadata"][ - "title"] == "Search for pseudoscalar bosons decaying into $e^+e^-$ pairs in the NA64 experiment at the CERN SPS" + assert ( + record.data["metadata"]["title"] + == "Search for pseudoscalar bosons decaying into $e^+e^-$ pairs in the NA64 experiment at the CERN SPS" + ) # check files replaced # when we manage non-CDS record - we trust INSPIRE as a source of truth # therefore files will be synced 1:1 with INSPIRE diff --git a/site/tests/utils.py b/site/tests/utils.py index d6d19694..51ac9cb5 100644 --- a/site/tests/utils.py +++ b/site/tests/utils.py @@ -15,4 +15,4 @@ def add_file_to_draft(draft_file_service, identity, draft, file_id): draft_file_service.set_file_content( identity, draft.id, file_id, BytesIO(b"test file content") ) - draft_file_service.commit_file(identity, draft.id, file_id) \ No newline at end of file + draft_file_service.commit_file(identity, draft.id, file_id) From 4881c422738ffda6b2001c7e485ae91a712ffc64 Mon Sep 17 00:00:00 2001 From: Karolina Przerwa Date: Tue, 13 Jan 2026 10:30:25 +0100 Subject: [PATCH 6/9] fix(harvester): change publication info expected type --- .../inspire_harvester/transform/mappers/basic_metadata.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/basic_metadata.py b/site/cds_rdm/inspire_harvester/transform/mappers/basic_metadata.py index 6957bd01..5e84f855 100644 --- a/site/cds_rdm/inspire_harvester/transform/mappers/basic_metadata.py +++ b/site/cds_rdm/inspire_harvester/transform/mappers/basic_metadata.py @@ -107,7 +107,8 @@ def map_value(self, src_metadata, ctx, logger): imprints = src_metadata.get("imprints", []) imprint_date = imprints[0].get("date") if imprints else None - publication_date = src_metadata.get("publication_info", {}).get("year") + publication_info = src_metadata.get("publication_info", []) + publication_date = publication_info[0].get("year") if publication_info else None creation_date = src_metadata.get("created") From 2096c40043af8e0c9e3781e6962bcbba11764b7b Mon Sep 17 00:00:00 2001 From: Karolina Przerwa Date: Tue, 13 Jan 2026 10:47:47 +0100 Subject: [PATCH 7/9] fix(tests): add top level conftest (needed for pytest 7+) --- conftest.py | 10 ++++++++++ site/tests/conftest.py | 2 -- 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 conftest.py diff --git a/conftest.py b/conftest.py new file mode 100644 index 00000000..05a248fc --- /dev/null +++ b/conftest.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2023 CERN. +# +# CDS-RDM is free software; you can redistribute it and/or modify it under +# the terms of the MIT License; see LICENSE file for more details. + +"""Pytest configuration.""" + +pytest_plugins = ("celery.contrib.pytest",) diff --git a/site/tests/conftest.py b/site/tests/conftest.py index 73c30bc9..293e1e7a 100644 --- a/site/tests/conftest.py +++ b/site/tests/conftest.py @@ -66,8 +66,6 @@ from .fake_datacite_client import FakeDataCiteClient -pytest_plugins = ("celery.contrib.pytest",) - class MockJinjaManifest(JinjaManifest): """Mock manifest.""" From 269f83c5089d5231424b3a127c5758adb8a3ac68 Mon Sep 17 00:00:00 2001 From: Karolina Przerwa Date: Wed, 14 Jan 2026 15:47:02 +0100 Subject: [PATCH 8/9] add(harvester): thesis custom fields * fix tests config --- .../inspire_harvester/transform/config.py | 9 +- .../transform/mappers/contributors.py | 2 +- .../transform/mappers/thesis.py | 41 +++ site/cds_rdm/inspire_harvester/writer.py | 9 +- site/tests/conftest.py | 21 +- .../data/completely_new_inspire_rec.json | 26 +- .../inspire_response_15_records_page_1.json | 292 ++++-------------- .../inspire_response_15_records_page_2.json | 42 +-- .../inspire_harvester/test_harvester_job.py | 148 ++++++++- .../inspire_harvester/test_transformer.py | 2 +- .../test_update_create_logic.py | 15 +- site/tests/inspire_harvester/utils.py | 5 +- 12 files changed, 312 insertions(+), 300 deletions(-) diff --git a/site/cds_rdm/inspire_harvester/transform/config.py b/site/cds_rdm/inspire_harvester/transform/config.py index 7fb95d22..509a1f28 100644 --- a/site/cds_rdm/inspire_harvester/transform/config.py +++ b/site/cds_rdm/inspire_harvester/transform/config.py @@ -35,7 +35,8 @@ ) from cds_rdm.inspire_harvester.transform.mappers.thesis import ( ThesisDefenceDateMapper, - ThesisPublicationDateMapper, + ThesisPublicationDateMapper, ThesisContributorsMapper, ThesisUniversityMappers, + ThesisTypeMappers, ) from cds_rdm.inspire_harvester.transform.policies import MapperPolicy from cds_rdm.inspire_harvester.transform.resource_types import ResourceType @@ -60,7 +61,10 @@ IdentifiersMapper(), RelatedIdentifiersMapper(), ) -THESIS_MAPPERS = (ThesisDefenceDateMapper(),) + +THESIS_MAPPERS = (ThesisDefenceDateMapper(), ThesisUniversityMappers(), + ThesisTypeMappers(), + ) inspire_mapper_policy = MapperPolicy(base=BASE_MAPPERS) @@ -75,6 +79,7 @@ ResourceType.THESIS, "metadata.publication_date", ): ThesisPublicationDateMapper(), + (ResourceType.THESIS, "metadata.contributors"): ThesisContributorsMapper(), }, ) diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/contributors.py b/site/cds_rdm/inspire_harvester/transform/mappers/contributors.py index 12f8d13b..2509a90b 100644 --- a/site/cds_rdm/inspire_harvester/transform/mappers/contributors.py +++ b/site/cds_rdm/inspire_harvester/transform/mappers/contributors.py @@ -88,7 +88,7 @@ def _transform_creatibutors(self, authors, ctx): ] = creator_identifiers if role: - rdm_creatibutor["role"] = role[0] + rdm_creatibutor["role"] = {"id": role[0]} creatibutors.append(rdm_creatibutor) return creatibutors except Exception as e: diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/thesis.py b/site/cds_rdm/inspire_harvester/transform/mappers/thesis.py index 8154729f..e2db8d5c 100644 --- a/site/cds_rdm/inspire_harvester/transform/mappers/thesis.py +++ b/site/cds_rdm/inspire_harvester/transform/mappers/thesis.py @@ -12,6 +12,7 @@ from babel_edtf import parse_edtf from edtf.parser.grammar import ParseException +from .contributors import ContributorsMapper from .mapper import MapperBase @@ -56,3 +57,43 @@ def map_value(self, src_metadata, ctx, logger): thesis_info = src_metadata.get("thesis_info", {}) defense_date = thesis_info.get("defense_date") return defense_date + + +@dataclass(frozen=True) +class ThesisUniversityMappers(MapperBase): + id = "custom_fields.thesis:thesis.university" + + def map_value(self, src_metadata, ctx, logger): + """Apply thesis field mapping.""" + thesis_info = src_metadata.get("thesis_info", {}) + institutions = thesis_info.get("institutions") + if institutions: + university = institutions[0].get("name") + return university + + + +@dataclass(frozen=True) +class ThesisTypeMappers(MapperBase): + id = "custom_fields.thesis:thesis.type" + + def map_value(self, src_metadata, ctx, logger): + """Apply thesis field mapping.""" + thesis_info = src_metadata.get("thesis_info", {}) + type = thesis_info.get("degree_type") + return type + + + +@dataclass(frozen=True) +class ThesisContributorsMapper(ContributorsMapper): + id = "metadata.contributors" + + def map_value(self, src_metadata, ctx, logger): + contributors = super().map_value(src_metadata, ctx, logger) + + _supervisors = src_metadata.get("supervisors") + supervisors = self._transform_creatibutors(_supervisors, ctx) + return contributors + supervisors + + diff --git a/site/cds_rdm/inspire_harvester/writer.py b/site/cds_rdm/inspire_harvester/writer.py index d3503f3b..b2742bd5 100644 --- a/site/cds_rdm/inspire_harvester/writer.py +++ b/site/cds_rdm/inspire_harvester/writer.py @@ -40,7 +40,6 @@ def _write_entry(self, stream_entry, *args, inspire_id=None, logger=None, **kwar existing_records_hits = existing_records.to_dict()["hits"]["hits"] existing_records_ids = [hit["id"] for hit in existing_records_hits] - if multiple_records_found: msg = "Multiple records match: {0}".format(", ".join(existing_records_ids)) @@ -448,11 +447,8 @@ def _create_new_record( except Exception as e: current_rdm_records_service.delete_draft(system_identity, draft["id"]) - logger.info(f"Draft {draft.id} is deleted due to errors.") + logger.error(f"Draft {draft.id} is deleted due to errors.") raise e - # raise WriterError( - # f"Failure: draft {draft.id} not created, unexpected error: {str(e)}." - # ) else: try: self._add_community(stream_entry, draft) @@ -476,9 +472,6 @@ def _create_new_record( except Exception as e: current_rdm_records_service.delete_draft(system_identity, draft["id"]) raise e - # raise WriterError( - # f"Failure: draft {draft.id} not published, unexpected error: {str(e)}." - # ) @hlog def _fetch_file( diff --git a/site/tests/conftest.py b/site/tests/conftest.py index 293e1e7a..f7c565c2 100644 --- a/site/tests/conftest.py +++ b/site/tests/conftest.py @@ -21,6 +21,8 @@ from invenio_access.permissions import superuser_access, system_identity from invenio_accounts import testutils from invenio_accounts.models import Role + +from cds_rdm.custom_fields import NAMESPACES, CUSTOM_FIELDS, CUSTOM_FIELDS_UI from invenio_administration.permissions import administration_access_action from invenio_app import factory as app_factory from invenio_cern_sync.users.profile import CERNUserProfileSchema @@ -34,7 +36,7 @@ RDM_PERSISTENT_IDENTIFIERS, RDM_RECORDS_IDENTIFIERS_SCHEMES, RDM_RECORDS_RELATED_IDENTIFIERS_SCHEMES, - always_valid, + always_valid, RDM_RECORDS_PERSONORG_SCHEMES, ) from invenio_rdm_records.resources.serializers import DataCite43JSONSerializer from invenio_rdm_records.services.pids import providers @@ -250,6 +252,16 @@ def app_config(app_config, mock_datacite_client): label=_("OAI ID"), ), ] + app_config["RDM_RECORDS_PERSONORG_SCHEMES"] = { + **RDM_RECORDS_PERSONORG_SCHEMES, + **{"inspire_author": {"label": _("Inspire"), + "validator": schemes.is_inspire_author, + "datacite": "INSPIRE"}, + "cds": {"label": _("CDS"), + "validator": schemes.is_cds, + "datacite": "CDS"} + } + } app_config["RDM_PARENT_PERSISTENT_IDENTIFIER_PROVIDERS"] = [ # DataCite Concept DOI provider providers.DataCitePIDProvider( @@ -260,6 +272,13 @@ def app_config(app_config, mock_datacite_client): ), ] app_config["RDM_LOCK_EDIT_PUBLISHED_FILES"] = lock_edit_record_published_files + app_config["RDM_NAMESPACES"] = { + # Custom fields + **NAMESPACES + } + app_config["RDM_CUSTOM_FIELDS" ]= CUSTOM_FIELDS + app_config["RDM_CUSTOM_FIELDS_UI"] = CUSTOM_FIELDS_UI + return app_config diff --git a/site/tests/inspire_harvester/data/completely_new_inspire_rec.json b/site/tests/inspire_harvester/data/completely_new_inspire_rec.json index 3be1fb6b..93db4582 100644 --- a/site/tests/inspire_harvester/data/completely_new_inspire_rec.json +++ b/site/tests/inspire_harvester/data/completely_new_inspire_rec.json @@ -4,19 +4,21 @@ "hits": [ { "metadata": { - "publication_info": { - "cnum": "C24-10-21.8", - "year": 2025, - "artid": "01165", - "journal_title": "EPJ Web Conf.", - "journal_record": { - "$ref": "https://inspirehep.net/api/journals/1211782" - }, - "journal_volume": "337", - "conference_record": { - "$ref": "https://inspirehep.net/api/conferences/2838772" + "publication_info": [ + { + "cnum": "C24-10-21.8", + "year": 2025, + "artid": "01165", + "journal_title": "EPJ Web Conf.", + "journal_record": { + "$ref": "https://inspirehep.net/api/journals/1211782" + }, + "journal_volume": "337", + "conference_record": { + "$ref": "https://inspirehep.net/api/conferences/2838772" + } } - }, + ], "documents": [ { "key": "4550b6ee36afc3fdedc08d0423375ab4", diff --git a/site/tests/inspire_harvester/data/inspire_response_15_records_page_1.json b/site/tests/inspire_harvester/data/inspire_response_15_records_page_1.json index 37aae4a1..e35995ac 100644 --- a/site/tests/inspire_harvester/data/inspire_response_15_records_page_1.json +++ b/site/tests/inspire_harvester/data/inspire_response_15_records_page_1.json @@ -252,11 +252,13 @@ ], "accelerator_experiments": [ { - "legacy_name": "CERN-LHC-CMS", - "curated_relation": true, - "record": { - "$ref": "https://inspirehep.net/api/experiments/1108642" - } + "accelerator": "LHC", + "experiment": "CMS", + "institution": "CERN", + "curated_relation": true, + "record": { + "$ref": "https://inspirehep.net/api/experiments/1108642" + } } ], "first_author": { @@ -430,77 +432,6 @@ "value": "Settore FIS/01 - Fisica Sperimentale" } ], - "references": [ - { - "reference": { - "dois": [ - "10.1016/0029-5582(61)90469-2" - ], - "label": "1", - "publication_info": { - "journal_volume": "22", - "page_end": "588", - "year": 1961, - "page_start": "579", - "journal_title": "Nucl.Phys." - }, - "title": { - "title": "Partial Symmetries of Weak Interactions" - }, - "misc": [ - "In:" - ], - "authors": [ - { - "full_name": "Glashow, S.L." - } - ] - }, - "raw_refs": [ - { - "schema": "text", - "value": "[1] S. L. Glashow. “Partial Symmetries of Weak Interactions”. In: Nucl. Phys. 22 (1961), pp. 579–588. DOI: 10.1016/0029-5582(61)90469-2." - } - ], - "record": { - "$ref": "https://inspirehep.net/api/literature/4328" - } - }, - { - "reference": { - "urls": [ - { - "value": "https://link.aps.org/doi/10" - } - ], - "dois": [ - "10.1103/PhysRevLett.19.1264" - ], - "label": "2", - "publication_info": { - "year": 1967 - }, - "title": { - "title": "A Model of Leptons" - }, - "misc": [ - "Steven Weinberg", - "In: Phys. Rev. Lett. 19 (21), pp. 1264- 1266", - "URL:", - "1103/PhysRevLett.19.1264" - ] - }, - "raw_refs": [ - { - "schema": "text", - "value": "[2] Steven Weinberg. “A Model of Leptons”. In: Phys. Rev. Lett. 19 (21 1967), pp. 1264– 1266. DOI: 10.1103/PhysRevLett.19.1264. URL: https://link.aps.org/doi/10. 1103/PhysRevLett.19.1264." - } - ], - "record": { - "$ref": "https://inspirehep.net/api/literature/51188" - } - } - ], "number_of_pages": 266, "author_count": 1, "earliest_date": "2024-05-29", @@ -571,11 +502,13 @@ ], "accelerator_experiments": [ { - "legacy_name": "CERN-LHC-LHCb", - "curated_relation": true, - "record": { - "$ref": "https://inspirehep.net/api/experiments/1110643" - } + "accelerator": "LHC", + "experiment": "LHCb", + "institution": "CERN", + "curated_relation": true, + "record": { + "$ref": "https://inspirehep.net/api/experiments/1110643" + } } ], "urls": [ @@ -800,124 +733,7 @@ ], "citation_count_without_self_citations": 0, "$schema": "https://inspirehep.net/schemas/records/hep.json", - "references": [ - { - "reference": { - "imprint": { - "publisher": "Elsevier" - }, - "publication_info": { - "year": 2008 - }, - "misc": [ - "The physics of ultraperipheral collisions at the LHC. Physics Reports", - "v. 458, n. 1-3, p. 1--171" - ], - "authors": [ - { - "full_name": "Baltz, A." - } - ] - }, - "raw_refs": [ - { - "schema": "text", - "source": "submitter", - "value": "BALTZ, A. et al. The physics of ultraperipheral collisions at the LHC. Physics Reports, Elsevier, v. 458, n. 1-3, p. 1--171, 2008." - } - ], - "curated_relation": true, - "record": { - "$ref": "https://inspirehep.net/api/literature/753911" - } - }, - { - "reference": { - "dois": [ - "10.1140/epjc/s10052-021-09314-2" - ], - "publication_info": { - "journal_volume": "81", - "page_end": "8", - "year": 2021, - "page_start": "1", - "journal_title": "Eur.Phys.J.C" - }, - "title": { - "title": "Searching for axionlike particles with low masses in pPb and PbPb collisions" - }, - "authors": [ - { - "full_name": "Goncalves, V." - } - ] - }, - "raw_refs": [ - { - "schema": "text", - "source": "submitter", - "value": "GONCALVES, V.; MARTINS, D.; RANGEL, M. Searching for axionlike particles with low masses in pPb and PbPb collisions. The European Physical Journal C, Springer, v. 81, n. 6, p. 1--8, 2021." - } - ], - "record": { - "$ref": "https://inspirehep.net/api/literature/1849509" - } - }, - { - "reference": { - "arxiv_eprint": "2102.08971", - "publication_info": { - "year": 2021 - }, - "misc": [ - "Collider constraints on axion-like particles. arXiv preprint" - ], - "authors": [ - { - "full_name": "D'Enterria, D." - } - ] - }, - "raw_refs": [ - { - "schema": "text", - "source": "submitter", - "value": "D'ENTERRIA, D. Collider constraints on axion-like particles. arXiv preprint arXiv:2102.08971, 2021" - } - ], - "record": { - "$ref": "https://inspirehep.net/api/literature/1847310" - } - } - ], "number_of_pages": 71, - "referenced_authors_bais": [ - "Zhi.Deng.1", - "L.Abadie.1", - "Souvik.Das.1", - "J.Thom.1", - "G.Chevenier.1", - "A.Strubig.1", - "I.G.Bearden.1", - "T.Buran.1", - "H.Ito.8", - "R.A.Rojas.1", - "F.Noto.1", - "Christian.Schmitt.1", - "A.Tcheremoukhine.1", - "T.M.Knight.1", - "M.Dragicevic.1", - "C.Sanzeni.1", - "J.Assahsah.1", - "S.Farinon.1", - "M.Hashemi.1", - "Niharika.Rout.1", - "L.Bosisio.1", - "J.Hauschildt.1", - "H.J.Bulten.8", - "Jose.Pretel.1", - "K.T.Knopfle.1" - ], "inspire_categories": [ { "term": "Experiment-HEP" @@ -925,10 +741,12 @@ ], "accelerator_experiments": [ { - "legacy_name": "CERN-LHC-LHCb", - "record": { - "$ref": "https://inspirehep.net/api/experiments/1110643" - } + "accelerator": "LHC", + "experiment": "LHCb", + "institution": "CERN", + "record": { + "$ref": "https://inspirehep.net/api/experiments/1110643" + } } ], "author_count": 1, @@ -952,10 +770,6 @@ "document_type": [ "thesis" ], - "texkeys": [ - "SantanaRangel:2024uyh", - "Curtis:2024wwm" - ], "languages": [ "en" ], @@ -1317,10 +1131,12 @@ ], "accelerator_experiments": [ { - "legacy_name": "CERN-LEP-ALEPH", - "record": { - "$ref": "https://inspirehep.net/api/experiments/1109311" - } + "accelerator": "LHC", + "experiment": "ALEPH", + "institution": "CERN", + "record": { + "$ref": "https://inspirehep.net/api/experiments/1109311" + } } ], "urls": [ @@ -1507,10 +1323,12 @@ "legacy_creation_date": "2018-08-09", "accelerator_experiments": [ { - "legacy_name": "CERN-LHC-CMS", - "record": { - "$ref": "https://inspirehep.net/api/experiments/1108642" - } + "accelerator": "LHC", + "experiment": "CMS", + "institution": "CERN", + "record": { + "$ref": "https://inspirehep.net/api/experiments/1108642" + } } ], "author_count": 1, @@ -1770,10 +1588,11 @@ ], "accelerator_experiments": [ { - "legacy_name": "CERN-NA-062", - "record": { - "$ref": "https://inspirehep.net/api/experiments/1275752" - } + "experiment": "NA-62", + "institution": "CERN", + "record": { + "$ref": "https://inspirehep.net/api/experiments/1275752" + } } ], "author_count": 1, @@ -2090,10 +1909,10 @@ ], "accelerator_experiments": [ { - "legacy_name": "AMS", - "record": { - "$ref": "https://inspirehep.net/api/experiments/1110577" - } + "experiment": "AMS", + "record": { + "$ref": "https://inspirehep.net/api/experiments/1110577" + } }, { "accelerator": "CERN SPS" @@ -2216,10 +2035,11 @@ ], "accelerator_experiments": [ { - "legacy_name": "CERN-NA-062", - "record": { - "$ref": "https://inspirehep.net/api/experiments/1275752" - } + "experiment": "NA-062", + "institution": "CERN", + "record": { + "$ref": "https://inspirehep.net/api/experiments/1275752" + } } ], "author_count": 1, @@ -2565,10 +2385,12 @@ ], "accelerator_experiments": [ { - "legacy_name": "CERN-LHC-ALICE", - "record": { - "$ref": "https://inspirehep.net/api/experiments/1110642" - } + "accelerator": "LHC", + "experiment": "ALICE", + "institution": "CERN", + "record": { + "$ref": "https://inspirehep.net/api/experiments/1110642" + } } ], "urls": [ @@ -2948,10 +2770,12 @@ ], "accelerator_experiments": [ { - "legacy_name": "CERN-LHC-CMS", - "record": { - "$ref": "https://inspirehep.net/api/experiments/1108642" - } + "accelerator": "LHC", + "experiment": "CMS", + "institution": "CERN", + "record": { + "$ref": "https://inspirehep.net/api/experiments/1108642" + } } ], "urls": [ diff --git a/site/tests/inspire_harvester/data/inspire_response_15_records_page_2.json b/site/tests/inspire_harvester/data/inspire_response_15_records_page_2.json index e7610b4c..42b3e78d 100644 --- a/site/tests/inspire_harvester/data/inspire_response_15_records_page_2.json +++ b/site/tests/inspire_harvester/data/inspire_response_15_records_page_2.json @@ -249,10 +249,12 @@ ], "accelerator_experiments": [ { - "legacy_name": "CERN-LHC-CMS", - "record": { - "$ref": "https://inspirehep.net/api/experiments/1108642" - } + "accelerator": "LHC", + "experiment": "CMS", + "institution": "CERN", + "record": { + "$ref": "https://inspirehep.net/api/experiments/1108642" + } } ], "urls": [ @@ -580,11 +582,12 @@ ], "accelerator_experiments": [ { - "legacy_name": "CERN-NA-058", - "curated_relation": true, - "record": { - "$ref": "https://inspirehep.net/api/experiments/1110255" - } + "experiment": "NA-058", + "institution": "CERN", + "curated_relation": true, + "record": { + "$ref": "https://inspirehep.net/api/experiments/1110255" + } } ], "urls": [ @@ -806,10 +809,12 @@ ], "accelerator_experiments": [ { - "legacy_name": "CERN-LHC-ALICE", - "record": { - "$ref": "https://inspirehep.net/api/experiments/1110642" - } + "accelerator": "LHC", + "experiment": "ALICE", + "institution": "CERN", + "record": { + "$ref": "https://inspirehep.net/api/experiments/1110642" + } } ], "author_count": 1, @@ -1163,11 +1168,12 @@ ], "accelerator_experiments": [ { - "legacy_name": "MUON-COLLIDER", - "curated_relation": true, - "record": { - "$ref": "https://inspirehep.net/api/experiments/2135913" - } + "accelerator": "MUON-COLLIDER", + "institution": "CERN", + "curated_relation": true, + "record": { + "$ref": "https://inspirehep.net/api/experiments/2135913" + } }, { "legacy_name": "CERN-LHC-CMS", diff --git a/site/tests/inspire_harvester/test_harvester_job.py b/site/tests/inspire_harvester/test_harvester_job.py index 20293ecb..ac182041 100644 --- a/site/tests/inspire_harvester/test_harvester_job.py +++ b/site/tests/inspire_harvester/test_harvester_job.py @@ -7,6 +7,7 @@ """ISNPIRE harvester job tests.""" import json +from pathlib import Path import pytest from invenio_access.permissions import system_identity @@ -16,6 +17,8 @@ from .utils import mock_requests_get, run_harvester_mock +DATA_DIR = Path(__file__).parent / "data" + expected_result_1 = { "metadata": { "resource_type": { @@ -33,6 +36,46 @@ "affiliations": [{"name": "Budapest, Tech. U."}], } ], + 'contributors': [ + { + 'affiliations': [ + { + 'name': 'Budapest, Tech. U.', + }, + ], + 'person_or_org': { + 'family_name': 'DiCaprio', + 'given_name': 'Leonardo', + 'name': 'DiCaprio, Leonardo', + 'type': 'personal', + }, + 'role': { + 'id': 'supervisor', + 'title': { + 'en': 'Supervisor', + }, + }, + }, + { + 'affiliations': [ + { + 'name': 'Wigner RCP, Budapest', + }, + ], + 'person_or_org': { + 'family_name': 'Bullock', + 'given_name': 'Sandra', + 'name': 'Bullock, Sandra', + 'type': 'personal', + }, + 'role': { + 'id': 'supervisor', + 'title': { + 'en': 'Supervisor', + }, + }, + }, + ], "title": "Fragmentation through Heavy and Light-flavor Measurements with the LHC ALICE Experiment", "publication_date": "2024", "languages": [{"id": "eng", "title": {"en": "English", "da": "Engelsk"}}], @@ -60,7 +103,27 @@ ], "description": "A few microseconds after the Big Bang, the universe was filled with an extremely hot and dense mixture of particles moving at near light speed.", }, - "custom_fields": {}, + "custom_fields": {'cern:accelerators': [ + { + 'id': 'CERN LHC', + 'title': { + 'en': 'CERN LHC', + }, + }, + ], + 'cern:experiments': [ + { + 'id': 'ALICE', + 'title': { + 'en': 'ALICE', + }, + }, + ], + 'thesis:thesis': { + + 'type': 'PhD', + 'university': 'Budapest, Tech. U.', + }, }, } expected_result_2 = { @@ -80,6 +143,22 @@ "affiliations": [{"name": "U. Grenoble Alpes"}], } ], + 'contributors': [ + { + 'person_or_org': { + 'family_name': 'Parker', + 'given_name': 'Sylvia', + 'name': 'Parker, Sylvia', + 'type': 'personal', + }, + 'role': { + 'id': 'supervisor', + 'title': { + 'en': 'Supervisor', + }, + }, + }, + ], "title": "Performance of the Electromagnetic Calorimeter of AMS-02 on the International Space Station ans measurement of the positronic fraction in the 1.5 – 350 GeV energy range", "publication_date": "2014", "subjects": [ @@ -120,7 +199,12 @@ ], "description": "The AMS-02 experiment is a particle detector installed on the International Space Station (ISS) since May 2011, which measures the characteristics of the cosmic rays to bring answers to the problematics risen by the astroparticle physics since a few decades, in particular the study of dark matter and the search of antimatter. The phenomenological aspects of the physics of cosmic rays are reviewed in a first part.", }, - "custom_fields": {}, + "custom_fields": { + 'thesis:thesis': { + 'type': 'PhD', + 'university': 'U. Grenoble Alpes', + }, + }, } expected_result_3 = { @@ -140,6 +224,27 @@ "affiliations": [{"name": "San Luis Potosi U."}], } ], + 'contributors': [ + { + 'affiliations': [ + { + 'name': 'San Luis Potosi U.', + }, + ], + 'person_or_org': { + 'family_name': 'Termopolis', + 'given_name': 'Mia', + 'name': 'Termopolis, Mia', + 'type': 'personal', + }, + 'role': { + 'id': 'supervisor', + 'title': { + 'en': 'Supervisor', + }, + }, + }, + ], "title": "Medición del tiempo de vida del K+ en el experimento NA62", "publication_date": "2024-05", "languages": [{"id": "spa", "title": {"en": "Spanish"}}], @@ -167,7 +272,8 @@ ], "description": "In the present study the possibility of measuring the lifetime of the positively charged Kaon , K+, is investigated , by using data and framework produced by the experiment NA62 of the European Organization for Nuclear Research (CERN).", }, - "custom_fields": {}, + "custom_fields": {"thesis:thesis": {'type': 'Bachelor', + 'university': 'San Luis Potosi U.', }}, } @@ -208,14 +314,11 @@ def test_inspire_job(running_app, scientific_community): } def mock_requests_get_pagination( - url, headers={"Accept": "application/json"}, stream=True + url, headers={"Accept": "application/vnd+inspire.record.expanded+json"}, + stream=True ): - page_1_file = ( - "tests/inspire_harvester/data/inspire_response_15_records_page_1.json" - ) - page_2_file = ( - "tests/inspire_harvester/data/inspire_response_15_records_page_2.json" - ) + page_1_file = DATA_DIR / "inspire_response_15_records_page_1.json" + page_2_file = DATA_DIR / "inspire_response_15_records_page_2.json" url_page_1 = "https://inspirehep.net/api/literature?q=_oai.sets%3AForCDS+AND+du+%3E%3D+2024-11-15+AND+du+%3C%3D+2025-01-09" url_page_2 = "https://inspirehep.net/api/literature/?q=_oai.sets%3AForCDS+AND+du+%3E%3D+2024-11-15+AND+du+%3C%3D+2025-01-09&size=10&page=2" @@ -228,8 +331,8 @@ def mock_requests_get_pagination( content = "" if filepath: with open( - filepath, - "r", + filepath, + "r", ) as f: content = json.load(f) @@ -241,9 +344,22 @@ def mock_requests_get_pagination( RDMRecord.index.refresh() created_records = current_rdm_records_service.search(system_identity) - assert created_records.total == 5 - tranformation(created_records.to_dict()["hits"]["hits"][0]["id"], expected_result_1) + assert created_records.total == 9 + + created_record1 = current_rdm_records_service.search( + system_identity, + params={"q": f"metadata.related_identifiers.identifier:2840463"}) + + created_record2 = current_rdm_records_service.search( + system_identity, + params={"q": f"metadata.related_identifiers.identifier:1452604"}) + + created_record3 = current_rdm_records_service.search( + system_identity, + params={"q": f"metadata.related_identifiers.identifier:2802969"}) + + tranformation(created_record1.to_dict()["hits"]["hits"][0]["id"], expected_result_1) - tranformation(created_records.to_dict()["hits"]["hits"][1]["id"], expected_result_2) + tranformation(created_record2.to_dict()["hits"]["hits"][0]["id"], expected_result_2) - tranformation(created_records.to_dict()["hits"]["hits"][2]["id"], expected_result_3) + tranformation(created_record3.to_dict()["hits"]["hits"][0]["id"], expected_result_3) diff --git a/site/tests/inspire_harvester/test_transformer.py b/site/tests/inspire_harvester/test_transformer.py index 54a51a3f..d6428c02 100644 --- a/site/tests/inspire_harvester/test_transformer.py +++ b/site/tests/inspire_harvester/test_transformer.py @@ -398,7 +398,7 @@ def test_transform_creatibutors(running_app): assert author["person_or_org"]["given_name"] == "John" assert author["person_or_org"]["family_name"] == "Doe" assert author["person_or_org"]["name"] == "Doe, John" - assert author["role"] == "author" + assert author["role"] == {"id": "author"} assert author["affiliations"] == [{"name": "CERN"}] assert author["person_or_org"]["identifiers"] == [ {"identifier": "0000-0000-0000-0000", "scheme": "orcid"} diff --git a/site/tests/inspire_harvester/test_update_create_logic.py b/site/tests/inspire_harvester/test_update_create_logic.py index 1bb0154a..2d99937b 100644 --- a/site/tests/inspire_harvester/test_update_create_logic.py +++ b/site/tests/inspire_harvester/test_update_create_logic.py @@ -1,5 +1,6 @@ import json from functools import partial +from pathlib import Path from time import sleep from unittest.mock import Mock, patch @@ -16,6 +17,8 @@ from ..utils import add_file_to_draft from .utils import mock_requests_get, run_harvester_mock +DATA_DIR = Path(__file__).parent / "data" + def test_new_non_CDS_record( running_app, location, scientific_community, datastream_config @@ -23,7 +26,7 @@ def test_new_non_CDS_record( """Test new non-CDS origin record.""" with open( - "tests/inspire_harvester/data/completely_new_inspire_rec.json", + DATA_DIR / "completely_new_inspire_rec.json", "r", ) as f: new_record = json.load(f) @@ -68,7 +71,7 @@ def test_CDS_DOI_create_record_fails( ): """Test insert record with CDS DOI - no record matched (deleted?).""" with open( - "tests/inspire_harvester/data/record_with_cds_DOI.json", + DATA_DIR / "record_with_cds_DOI.json", "r", ) as f: new_record = json.load(f) @@ -102,7 +105,7 @@ def test_update_record_with_CDS_DOI_one_doc_type( record = current_rdm_records_service.publish(system_identity, draft.id) with open( - "tests/inspire_harvester/data/record_with_cds_DOI.json", + DATA_DIR / "record_with_cds_DOI.json", "r", ) as f: new_record = json.load(f) @@ -138,7 +141,7 @@ def test_update_record_with_CDS_DOI_one_doc_type( assert { "identifier": "2707794", "scheme": "inspire", - "relation_type": {"id": "isvariantof"}, + "relation_type": {"id": "isvariantformof"}, "resource_type": {"id": "publication-other"}, } in new_version.data["metadata"]["related_identifiers"] @@ -191,7 +194,7 @@ def test_update_no_CDS_DOI_multiple_doc_types( RDMRecord.index.refresh() with open( - "tests/inspire_harvester/data/record_with_no_cds_DOI_multiple_doc_type.json", + DATA_DIR / "record_with_no_cds_DOI_multiple_doc_type.json", "r", ) as f: new_record = json.load(f) @@ -242,7 +245,7 @@ def test_update_no_CDS_DOI_from_metadata_only_to_files( RDMRecord.index.refresh() with open( - "tests/inspire_harvester/data/record_with_no_cds_DOI_multiple_doc_type2.json", + DATA_DIR / "record_with_no_cds_DOI_multiple_doc_type2.json", "r", ) as f: new_record = json.load(f) diff --git a/site/tests/inspire_harvester/utils.py b/site/tests/inspire_harvester/utils.py index 65411761..29eb2b0b 100644 --- a/site/tests/inspire_harvester/utils.py +++ b/site/tests/inspire_harvester/utils.py @@ -7,11 +7,14 @@ """Pytest utils module.""" from io import BytesIO +from pathlib import Path from unittest.mock import Mock, patch from celery import current_app from invenio_vocabularies.services.tasks import process_datastream +DATA_DIR = Path(__file__).parent / "data" + def mock_requests_get( url, mock_content, headers={"Accept": "application/json"}, stream=True @@ -21,7 +24,7 @@ def mock_requests_get( mock_response.status_code = 200 if "files" in url: with open( - "tests/inspire_harvester/data/inspire_file.bin", + DATA_DIR / "inspire_file.bin", "rb", ) as f: mock_content = f.read() From 83c7c097b57da4e91613d23d46ec3cbbe4f921c0 Mon Sep 17 00:00:00 2001 From: Karolina Przerwa Date: Thu, 15 Jan 2026 13:07:14 +0100 Subject: [PATCH 9/9] fix(harvester): licenses and docstrings --- site/cds_rdm/inspire_harvester/__init__.py | 6 ++--- site/cds_rdm/inspire_harvester/jobs.py | 4 +-- site/cds_rdm/inspire_harvester/reader.py | 4 +-- .../inspire_harvester/transform/__init__.py | 4 +-- .../inspire_harvester/transform/config.py | 6 +++-- .../inspire_harvester/transform/context.py | 4 ++- .../transform/mappers/__init__.py | 8 ++++++ .../transform/mappers/basic_metadata.py | 25 +++++++++++++++++-- .../transform/mappers/contributors.py | 10 +++++++- .../transform/mappers/custom_fields.py | 5 ++-- .../transform/mappers/files.py | 5 +++- .../transform/mappers/identifiers.py | 9 ++++++- .../transform/mappers/mapper.py | 12 +++++++++ .../transform/mappers/thesis.py | 11 +++++++- .../inspire_harvester/transform/policies.py | 2 +- .../transform/resource_types.py | 5 ++-- .../transform/transform_entry.py | 2 +- .../inspire_harvester/transform/utils.py | 5 ++-- site/cds_rdm/inspire_harvester/transformer.py | 4 +-- site/cds_rdm/inspire_harvester/writer.py | 4 +-- site/tests/conftest.py | 6 ++--- 21 files changed, 108 insertions(+), 33 deletions(-) diff --git a/site/cds_rdm/inspire_harvester/__init__.py b/site/cds_rdm/inspire_harvester/__init__.py index 41ffc39f..f8636ccb 100644 --- a/site/cds_rdm/inspire_harvester/__init__.py +++ b/site/cds_rdm/inspire_harvester/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2025 CERN. +# Copyright (C) 2026 CERN. # # CDS-RDM is free software; you can redistribute it and/or modify it under -# the terms of the GPL-2.0 License; see LICENSE file for more details. +# the terms of the MIT License; see LICENSE file for more details. -"""INSPIRE to CDS harvester module.""" +"""INSPIRE to CDS harvester context module.""" \ No newline at end of file diff --git a/site/cds_rdm/inspire_harvester/jobs.py b/site/cds_rdm/inspire_harvester/jobs.py index a180c985..1a11eccf 100644 --- a/site/cds_rdm/inspire_harvester/jobs.py +++ b/site/cds_rdm/inspire_harvester/jobs.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2025 CERN. +# Copyright (C) 2026 CERN. # # CDS-RDM is free software; you can redistribute it and/or modify it under -# the terms of the GPL-2.0 License; see LICENSE file for more details. +# the terms of the MIT License; see LICENSE file for more details. """Jobs module.""" diff --git a/site/cds_rdm/inspire_harvester/reader.py b/site/cds_rdm/inspire_harvester/reader.py index 3363c3bd..417c7441 100644 --- a/site/cds_rdm/inspire_harvester/reader.py +++ b/site/cds_rdm/inspire_harvester/reader.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2025 CERN. +# Copyright (C) 2026 CERN. # # CDS-RDM is free software; you can redistribute it and/or modify it under -# the terms of the GPL-2.0 License; see LICENSE file for more details. +# the terms of the MIT License; see LICENSE file for more details. """Reader component.""" from urllib.parse import urlencode diff --git a/site/cds_rdm/inspire_harvester/transform/__init__.py b/site/cds_rdm/inspire_harvester/transform/__init__.py index 30d07c8d..f8636ccb 100644 --- a/site/cds_rdm/inspire_harvester/transform/__init__.py +++ b/site/cds_rdm/inspire_harvester/transform/__init__.py @@ -3,6 +3,6 @@ # Copyright (C) 2026 CERN. # # CDS-RDM is free software; you can redistribute it and/or modify it under -# the terms of the GPL-2.0 License; see LICENSE file for more details. +# the terms of the MIT License; see LICENSE file for more details. -"""INSPIRE to CDS harvester module.""" +"""INSPIRE to CDS harvester context module.""" \ No newline at end of file diff --git a/site/cds_rdm/inspire_harvester/transform/config.py b/site/cds_rdm/inspire_harvester/transform/config.py index 509a1f28..2f2c9d33 100644 --- a/site/cds_rdm/inspire_harvester/transform/config.py +++ b/site/cds_rdm/inspire_harvester/transform/config.py @@ -3,7 +3,7 @@ # Copyright (C) 2026 CERN. # # CDS-RDM is free software; you can redistribute it and/or modify it under -# the terms of the GPL-2.0 License; see LICENSE file for more details. +# the terms of the MIT License; see LICENSE file for more details. """INSPIRE to CDS harvester config module.""" @@ -34,9 +34,11 @@ RelatedIdentifiersMapper, ) from cds_rdm.inspire_harvester.transform.mappers.thesis import ( + ThesisContributorsMapper, ThesisDefenceDateMapper, - ThesisPublicationDateMapper, ThesisContributorsMapper, ThesisUniversityMappers, + ThesisPublicationDateMapper, ThesisTypeMappers, + ThesisUniversityMappers, ) from cds_rdm.inspire_harvester.transform.policies import MapperPolicy from cds_rdm.inspire_harvester.transform.resource_types import ResourceType diff --git a/site/cds_rdm/inspire_harvester/transform/context.py b/site/cds_rdm/inspire_harvester/transform/context.py index a5747756..a75f3440 100644 --- a/site/cds_rdm/inspire_harvester/transform/context.py +++ b/site/cds_rdm/inspire_harvester/transform/context.py @@ -3,7 +3,7 @@ # Copyright (C) 2026 CERN. # # CDS-RDM is free software; you can redistribute it and/or modify it under -# the terms of the GPL-2.0 License; see LICENSE file for more details. +# the terms of the MIT License; see LICENSE file for more details. """INSPIRE to CDS harvester context module.""" @@ -15,6 +15,8 @@ @dataclass(frozen=True) class MetadataSerializationContext: + """Metadata serializing context.""" + resource_type: ResourceType inspire_id: str errors: List[str] = field(default_factory=list) diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/__init__.py b/site/cds_rdm/inspire_harvester/transform/mappers/__init__.py index e69de29b..f8636ccb 100644 --- a/site/cds_rdm/inspire_harvester/transform/mappers/__init__.py +++ b/site/cds_rdm/inspire_harvester/transform/mappers/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2026 CERN. +# +# CDS-RDM is free software; you can redistribute it and/or modify it under +# the terms of the MIT License; see LICENSE file for more details. + +"""INSPIRE to CDS harvester context module.""" \ No newline at end of file diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/basic_metadata.py b/site/cds_rdm/inspire_harvester/transform/mappers/basic_metadata.py index 5e84f855..a95a3ae3 100644 --- a/site/cds_rdm/inspire_harvester/transform/mappers/basic_metadata.py +++ b/site/cds_rdm/inspire_harvester/transform/mappers/basic_metadata.py @@ -3,7 +3,7 @@ # Copyright (C) 2026 CERN. # # CDS-RDM is free software; you can redistribute it and/or modify it under -# the terms of the GPL-2.0 License; see LICENSE file for more details. +# the terms of the MIT License; see LICENSE file for more details. """INSPIRE to CDS harvester module.""" @@ -19,27 +19,35 @@ @dataclass(frozen=True) class ResourceTypeMapper(MapperBase): + """Resource type mapper.""" + id = "metadata.resource_type.id" def map_value(self, src_metadata, ctx, logger): - + """Map resource type value.""" return ctx.resource_type.value @dataclass(frozen=True) class TitleMapper(MapperBase): + """Title mapper.""" + id = "metadata.title" def map_value(self, src_metadata, ctx, logger): + """Map title value.""" inspire_titles = src_metadata.get("titles", []) return inspire_titles[0].get("title") @dataclass(frozen=True) class AdditionalTitlesMapper(MapperBase): + """Additional titles mapper.""" + id = "metadata.additional_titles" def map_value(self, src_metadata, ctx, logger): + """Map additional titles.""" inspire_titles = src_metadata.get("titles", []) rdm_additional_titles = [] for i, inspire_title in enumerate(inspire_titles[1:]): @@ -69,15 +77,19 @@ def map_value(self, src_metadata, ctx, logger): @dataclass(frozen=True) class PublisherMapper(MapperBase): + """Publisher mapper.""" + id = "metadata.publisher" def validate(self, src, ctx): + """Validate publisher data.""" imprints = src.get("imprints", []) if len(imprints) > 1: ctx.errors.append(f"More than 1 imprint found. INSPIRE#{ctx.inspire_id}.") def map_value(self, src_metadata, ctx, logger): + """Map publisher value.""" imprints = src_metadata.get("imprints", []) imprint = None publisher = None @@ -99,6 +111,7 @@ def map_value(self, src_metadata, ctx, logger): @dataclass(frozen=True) class PublicationDateMapper(MapperBase): + """Publication date mapper.""" id = "metadata.publication_date" @@ -128,6 +141,7 @@ def map_value(self, src_metadata, ctx, logger): @dataclass(frozen=True) class CopyrightMapper(MapperBase): + """Copyright mapper.""" id = "metadata.copyright" @@ -161,6 +175,7 @@ def map_value(self, src_metadata, ctx, logger): @dataclass(frozen=True) class DescriptionMapper(MapperBase): + """Description mapper.""" id = "metadata.description" @@ -173,6 +188,8 @@ def map_value(self, src_metadata, ctx, logger): @dataclass(frozen=True) class AdditionalDescriptionsMapper(MapperBase): + """Additional descriptions mapper.""" + id = "metadata.additional_descriptions" def map_value(self, src_metadata, ctx, logger): @@ -205,6 +222,8 @@ def map_value(self, src_metadata, ctx, logger): @dataclass(frozen=True) class SubjectsMapper(MapperBase): + """Subjects mapper.""" + id = "metadata.subjects" def map_value(self, src_metadata, ctx, logger): @@ -225,6 +244,8 @@ def map_value(self, src_metadata, ctx, logger): @dataclass(frozen=True) class LanguagesMapper(MapperBase): + """Languages mapper.""" + id = "metadata.languages" def map_value(self, src_metadata, ctx, logger): diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/contributors.py b/site/cds_rdm/inspire_harvester/transform/mappers/contributors.py index 2509a90b..487d0219 100644 --- a/site/cds_rdm/inspire_harvester/transform/mappers/contributors.py +++ b/site/cds_rdm/inspire_harvester/transform/mappers/contributors.py @@ -3,7 +3,7 @@ # Copyright (C) 2026 CERN. # # CDS-RDM is free software; you can redistribute it and/or modify it under -# the terms of the GPL-2.0 License; see LICENSE file for more details. +# the terms of the MIT License; see LICENSE file for more details. """INSPIRE to CDS harvester module.""" @@ -13,6 +13,7 @@ class CreatibutorsMapper(MapperBase): + """Base class for mapping creatibutors (creators and contributors).""" def _transform_author_identifiers(self, author): """Transform ids of authors. Keeping only ORCID and CDS.""" @@ -98,6 +99,7 @@ def _transform_creatibutors(self, authors, ctx): return None def map_value(self, src, ctx, logger): + """Map creatibutors value (to be implemented by subclasses).""" pass @@ -106,9 +108,12 @@ def map_value(self, src, ctx, logger): @dataclass(frozen=True) class AuthorsMapper(CreatibutorsMapper): + """Mapper for authors/creators.""" + id = "metadata.creators" def map_value(self, src_metadata, ctx, logger): + """Map authors to RDM creators.""" authors = src_metadata.get("authors", []) creators = [] for author in authors: @@ -136,9 +141,12 @@ def map_value(self, src_metadata, ctx, logger): @dataclass(frozen=True) class ContributorsMapper(CreatibutorsMapper): + """Mapper for contributors.""" + id = "metadata.contributors" def map_value(self, src_metadata, ctx, logger): + """Map authors to RDM contributors.""" authors = src_metadata.get("authors", []) contributors = [] diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/custom_fields.py b/site/cds_rdm/inspire_harvester/transform/mappers/custom_fields.py index e8fddf80..0fa0b5b1 100644 --- a/site/cds_rdm/inspire_harvester/transform/mappers/custom_fields.py +++ b/site/cds_rdm/inspire_harvester/transform/mappers/custom_fields.py @@ -3,7 +3,7 @@ # Copyright (C) 2026 CERN. # # CDS-RDM is free software; you can redistribute it and/or modify it under -# the terms of the GPL-2.0 License; see LICENSE file for more details. +# the terms of the MIT License; see LICENSE file for more details. """INSPIRE to CDS harvester module.""" @@ -17,6 +17,7 @@ @dataclass(frozen=True) class ImprintMapper(MapperBase): + """Mapper for imprint custom fields.""" id = "custom_fields.imprint:imprint" @@ -53,7 +54,7 @@ def map_value(self, src_metadata, ctx, logger): @dataclass(frozen=True) class CERNFieldsMapper(MapperBase): - """Map CERN specific custom fields""" + """Map CERN specific custom fields.""" id = "custom_fields" diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/files.py b/site/cds_rdm/inspire_harvester/transform/mappers/files.py index 0bbcc556..e8ee7b4c 100644 --- a/site/cds_rdm/inspire_harvester/transform/mappers/files.py +++ b/site/cds_rdm/inspire_harvester/transform/mappers/files.py @@ -3,7 +3,7 @@ # Copyright (C) 2026 CERN. # # CDS-RDM is free software; you can redistribute it and/or modify it under -# the terms of the GPL-2.0 License; see LICENSE file for more details. +# the terms of the MIT License; see LICENSE file for more details. """INSPIRE to CDS harvester module.""" @@ -14,9 +14,12 @@ @dataclass(frozen=True) class FilesMapper(MapperBase): + """Mapper for files.""" + id = "files" def map_value(self, src_metadata, ctx, logger): + """Map files from INSPIRE documents to RDM files.""" logger.debug(f"Starting _transform_files") rdm_files_entries = {} diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/identifiers.py b/site/cds_rdm/inspire_harvester/transform/mappers/identifiers.py index f0b0a616..b16a66cf 100644 --- a/site/cds_rdm/inspire_harvester/transform/mappers/identifiers.py +++ b/site/cds_rdm/inspire_harvester/transform/mappers/identifiers.py @@ -3,7 +3,7 @@ # Copyright (C) 2026 CERN. # # CDS-RDM is free software; you can redistribute it and/or modify it under -# the terms of the GPL-2.0 License; see LICENSE file for more details. +# the terms of the MIT License; see LICENSE file for more details. """INSPIRE to CDS harvester module.""" @@ -19,6 +19,8 @@ @dataclass(frozen=True) class DOIMapper(MapperBase): + """Mapper for DOI identifiers.""" + id = "pids" def map_value(self, src_metadata, ctx, logger): @@ -61,9 +63,12 @@ def map_value(self, src_metadata, ctx, logger): @dataclass(frozen=True) class IdentifiersMapper(MapperBase): + """Mapper for record identifiers.""" + id = "metadata.identifiers" def map_value(self, src_metadata, ctx, logger): + """Map identifiers from external system identifiers.""" identifiers = [] RDM_RECORDS_IDENTIFIERS_SCHEMES = current_app.config[ "RDM_RECORDS_IDENTIFIERS_SCHEMES" @@ -93,6 +98,8 @@ def map_value(self, src_metadata, ctx, logger): @dataclass(frozen=True) class RelatedIdentifiersMapper(MapperBase): + """Mapper for related identifiers.""" + id = "metadata.related_identifiers" def map_value(self, src_metadata, ctx, logger): diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/mapper.py b/site/cds_rdm/inspire_harvester/transform/mappers/mapper.py index 0e4ba28d..f79d44e9 100644 --- a/site/cds_rdm/inspire_harvester/transform/mappers/mapper.py +++ b/site/cds_rdm/inspire_harvester/transform/mappers/mapper.py @@ -1,13 +1,25 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2026 CERN. +# +# CDS-RDM is free software; you can redistribute it and/or modify it under +# the terms of the MIT License; see LICENSE file for more details. + +"""INSPIRE to CDS harvester context module.""" + from abc import ABC, abstractmethod from cds_rdm.inspire_harvester.transform.utils import set_path class MapperBase(ABC): + """Base class for metadata mappers.""" + id: str returns_patch: bool = False def apply(self, src_metadata, ctx, logger): + """Apply the mapper to source metadata and return the result.""" result = self.map_value(src_metadata, ctx, logger) if not result: return diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/thesis.py b/site/cds_rdm/inspire_harvester/transform/mappers/thesis.py index e2db8d5c..8149683d 100644 --- a/site/cds_rdm/inspire_harvester/transform/mappers/thesis.py +++ b/site/cds_rdm/inspire_harvester/transform/mappers/thesis.py @@ -3,7 +3,7 @@ # Copyright (C) 2026 CERN. # # CDS-RDM is free software; you can redistribute it and/or modify it under -# the terms of the GPL-2.0 License; see LICENSE file for more details. +# the terms of the MIT License; see LICENSE file for more details. """INSPIRE to CDS harvester module.""" @@ -18,6 +18,7 @@ @dataclass(frozen=True) class ThesisPublicationDateMapper(MapperBase): + """Mapper for thesis publication date.""" id = "metadata.publication_date" @@ -49,6 +50,7 @@ def map_value(self, src_metadata, ctx, logger): @dataclass(frozen=True) class ThesisDefenceDateMapper(MapperBase): + """Mapper for thesis defence date.""" id = "custom_fields.thesis:thesis.defense_date" @@ -61,6 +63,8 @@ def map_value(self, src_metadata, ctx, logger): @dataclass(frozen=True) class ThesisUniversityMappers(MapperBase): + """Mapper for thesis university.""" + id = "custom_fields.thesis:thesis.university" def map_value(self, src_metadata, ctx, logger): @@ -75,6 +79,8 @@ def map_value(self, src_metadata, ctx, logger): @dataclass(frozen=True) class ThesisTypeMappers(MapperBase): + """Mapper for thesis type.""" + id = "custom_fields.thesis:thesis.type" def map_value(self, src_metadata, ctx, logger): @@ -87,9 +93,12 @@ def map_value(self, src_metadata, ctx, logger): @dataclass(frozen=True) class ThesisContributorsMapper(ContributorsMapper): + """Mapper for thesis contributors including supervisors.""" + id = "metadata.contributors" def map_value(self, src_metadata, ctx, logger): + """Map thesis contributors and supervisors.""" contributors = super().map_value(src_metadata, ctx, logger) _supervisors = src_metadata.get("supervisors") diff --git a/site/cds_rdm/inspire_harvester/transform/policies.py b/site/cds_rdm/inspire_harvester/transform/policies.py index 26f75e2a..637a999b 100644 --- a/site/cds_rdm/inspire_harvester/transform/policies.py +++ b/site/cds_rdm/inspire_harvester/transform/policies.py @@ -3,7 +3,7 @@ # Copyright (C) 2026 CERN. # # CDS-RDM is free software; you can redistribute it and/or modify it under -# the terms of the GPL-2.0 License; see LICENSE file for more details. +# the terms of the MIT License; see LICENSE file for more details. """INSPIRE to CDS policies module.""" diff --git a/site/cds_rdm/inspire_harvester/transform/resource_types.py b/site/cds_rdm/inspire_harvester/transform/resource_types.py index f0656769..d798bdef 100644 --- a/site/cds_rdm/inspire_harvester/transform/resource_types.py +++ b/site/cds_rdm/inspire_harvester/transform/resource_types.py @@ -3,7 +3,7 @@ # Copyright (C) 2026 CERN. # # CDS-RDM is free software; you can redistribute it and/or modify it under -# the terms of the GPL-2.0 License; see LICENSE file for more details. +# the terms of the MIT License; see LICENSE file for more details. """INSPIRE to CDS harvester module.""" @@ -11,6 +11,8 @@ class ResourceType(str, Enum): + """Enumeration of resource types for CDS-RDM.""" + ARTICLE = "publication-article" BOOK = "publication-book" BOOK_CHAPTER = "publication-bookchapter" @@ -48,7 +50,6 @@ def __init__(self, inspire_id, logger): def _select_document_type(self, doc_types): """Select document types.""" - priority = { v: i for i, v in enumerate( diff --git a/site/cds_rdm/inspire_harvester/transform/transform_entry.py b/site/cds_rdm/inspire_harvester/transform/transform_entry.py index 7f9ca527..02eb51ca 100644 --- a/site/cds_rdm/inspire_harvester/transform/transform_entry.py +++ b/site/cds_rdm/inspire_harvester/transform/transform_entry.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2025 CERN. +# Copyright (C) 2025-2026 CERN. # # CDS-RDM is free software; you can redistribute it and/or modify it under # the terms of the MIT License; see LICENSE file for more details. diff --git a/site/cds_rdm/inspire_harvester/transform/utils.py b/site/cds_rdm/inspire_harvester/transform/utils.py index 7a124d0c..2cd5508d 100644 --- a/site/cds_rdm/inspire_harvester/transform/utils.py +++ b/site/cds_rdm/inspire_harvester/transform/utils.py @@ -3,7 +3,7 @@ # Copyright (C) 2026 CERN. # # CDS-RDM is free software; you can redistribute it and/or modify it under -# the terms of the GPL-2.0 License; see LICENSE file for more details. +# the terms of the MIT License; see LICENSE file for more details. """INSPIRE to CDS harvester module.""" @@ -16,6 +16,7 @@ def assert_unique_ids(mappers): + """Assert that all mapper IDs are unique.""" ids = [m.id for m in mappers] counts = Counter(ids) dupes = [mid for mid, c in counts.items() if c > 1] @@ -47,6 +48,7 @@ def deep_merge(a, b): def deep_merge_all(parts): + """Deep merge all parts into a single dictionary.""" out = {} for p in parts: if p is not None: @@ -56,7 +58,6 @@ def deep_merge_all(parts): def search_vocabulary(term, vocab_type, ctx, logger): """Search vocabulary utility function.""" - service = current_service_registry.get("vocabularies") if "/" in term: # escape the slashes diff --git a/site/cds_rdm/inspire_harvester/transformer.py b/site/cds_rdm/inspire_harvester/transformer.py index 5b0e911b..1d606939 100644 --- a/site/cds_rdm/inspire_harvester/transformer.py +++ b/site/cds_rdm/inspire_harvester/transformer.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2025 CERN. +# Copyright (C) 2026 CERN. # # CDS-RDM is free software; you can redistribute it and/or modify it under -# the terms of the GPL-2.0 License; see LICENSE file for more details. +# the terms of the MIT License; see LICENSE file for more details. """Transformer module.""" from flask import current_app diff --git a/site/cds_rdm/inspire_harvester/writer.py b/site/cds_rdm/inspire_harvester/writer.py index b2742bd5..235ce77d 100644 --- a/site/cds_rdm/inspire_harvester/writer.py +++ b/site/cds_rdm/inspire_harvester/writer.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2025 CERN. +# Copyright (C) 2026 CERN. # # CDS-RDM is free software; you can redistribute it and/or modify it under -# the terms of the GPL-2.0 License; see LICENSE file for more details. +# the terms of the MIT License; see LICENSE file for more details. """Writer module.""" import logging diff --git a/site/tests/conftest.py b/site/tests/conftest.py index f7c565c2..20690f8e 100644 --- a/site/tests/conftest.py +++ b/site/tests/conftest.py @@ -21,8 +21,6 @@ from invenio_access.permissions import superuser_access, system_identity from invenio_accounts import testutils from invenio_accounts.models import Role - -from cds_rdm.custom_fields import NAMESPACES, CUSTOM_FIELDS, CUSTOM_FIELDS_UI from invenio_administration.permissions import administration_access_action from invenio_app import factory as app_factory from invenio_cern_sync.users.profile import CERNUserProfileSchema @@ -35,8 +33,9 @@ RDM_PARENT_PERSISTENT_IDENTIFIERS, RDM_PERSISTENT_IDENTIFIERS, RDM_RECORDS_IDENTIFIERS_SCHEMES, + RDM_RECORDS_PERSONORG_SCHEMES, RDM_RECORDS_RELATED_IDENTIFIERS_SCHEMES, - always_valid, RDM_RECORDS_PERSONORG_SCHEMES, + always_valid, ) from invenio_rdm_records.resources.serializers import DataCite43JSONSerializer from invenio_rdm_records.services.pids import providers @@ -56,6 +55,7 @@ from invenio_vocabularies.records.api import Vocabulary from cds_rdm import schemes +from cds_rdm.custom_fields import CUSTOM_FIELDS, CUSTOM_FIELDS_UI, NAMESPACES from cds_rdm.inspire_harvester.reader import InspireHTTPReader from cds_rdm.inspire_harvester.transformer import InspireJsonTransformer from cds_rdm.inspire_harvester.writer import InspireWriter