From d63fc4730c41e50b5afc4d63908122fb304066a3 Mon Sep 17 00:00:00 2001 From: Mohammed Taha Khan Date: Mon, 19 Oct 2026 10:34:53 +0200 Subject: [PATCH] fix(hr): accept REPORT/BROCHURE scoped by 710 HR --- .../rdm/records/transform/models/hr.py | 4 +- .../transform/xml_processing/rules/hr.py | 37 ++++++++++++++++++- tests/cds-rdm/test_hr_migration.py | 4 ++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/cds_migrator_kit/rdm/records/transform/models/hr.py b/cds_migrator_kit/rdm/records/transform/models/hr.py index fc9d8140..2e867b9e 100644 --- a/cds_migrator_kit/rdm/records/transform/models/hr.py +++ b/cds_migrator_kit/rdm/records/transform/models/hr.py @@ -25,7 +25,9 @@ class HrModel(CdsOverdo): 980__:CHISBULLETIN OR 980__:CCP OR 980__:CERN-ADMIN-E-GUIDE OR - 980__:HR-SMC + 980__:HR-SMC OR + (980__:REPORT AND 710__.5:HR) OR + (980__:BROCHURE AND 710__.5:HR) """ __ignore_keys__ = { diff --git a/cds_migrator_kit/rdm/records/transform/xml_processing/rules/hr.py b/cds_migrator_kit/rdm/records/transform/xml_processing/rules/hr.py index 8652a045..943ba465 100644 --- a/cds_migrator_kit/rdm/records/transform/xml_processing/rules/hr.py +++ b/cds_migrator_kit/rdm/records/transform/xml_processing/rules/hr.py @@ -4,6 +4,8 @@ from dateutil.parser import ParserError, parse from dojson.errors import IgnoreKey +from idutils.normalizers import normalize_isbn +from isbnlib import NotValidISBNError from cds_migrator_kit.errors import UnexpectedValue from cds_migrator_kit.transform.xml_processing.quality.decorators import ( @@ -24,6 +26,29 @@ from .research import journal, related_identifiers +@model.over("isbns", "^020__") +def isbn(self, key, value): + """Map ISBN (020 $a) into related_identifiers — e.g. CERN-HR-Note-2025-019.""" + _isbn = StringValue(value.get("a", "")).parse() + if not _isbn: + raise IgnoreKey("isbns") + try: + _isbn = normalize_isbn(_isbn) + except NotValidISBNError: + raise UnexpectedValue("Not a valid ISBN.", field=key, value=value) + new_id = { + "identifier": _isbn, + "scheme": "isbn", + "relation_type": {"id": "isvariantformof"}, + "resource_type": {"id": "publication-book"}, + } + ids = self.get("related_identifiers", []) + if new_id not in ids: + ids.append(new_id) + self["related_identifiers"] = ids + raise IgnoreKey("isbns") + + @model.over("access_grants", "^506[1_]_") @for_each_value def access_grants(self, key, value): @@ -131,6 +156,9 @@ def collection(self, key, value): "intnote", "operational circular", "publhr", + "pulhr", # typo seen on CERN-HR-Note leftovers + "report", + "cernofficialpressbrochure", ]: raise UnexpectedValue(subfield="a", value=value, field=key) raise IgnoreKey("collection") @@ -178,6 +206,9 @@ def resource_type(self, key, value): "hr-smc": {"id": "administrative-regulation"}, "ccp": {"id": "other"}, "conferencepaper": {"id": "publication-conferencepaper"}, + # CERN-HR-* leftovers (#623): generic 980 tags scoped via 710 $5 HR + "report": {"id": "publication-report"}, + "brochure": {"id": "publication-brochure"}, } try: @@ -261,7 +292,11 @@ def custom_fields(self, key, value): except IgnoreKey: author = None if author: - contributors.append(author[0]) + contributor = author[0] + # RDM requires a role on contributors (creators may omit it) + if "role" not in contributor: + contributor["role"] = {"id": "other"} + contributors.append(contributor) self["contributors"] = contributors raise IgnoreKey("administrative_unit") diff --git a/tests/cds-rdm/test_hr_migration.py b/tests/cds-rdm/test_hr_migration.py index c6e87d97..c43d1e75 100644 --- a/tests/cds-rdm/test_hr_migration.py +++ b/tests/cds-rdm/test_hr_migration.py @@ -242,6 +242,10 @@ def test_resource_type_mapping(): assert resource_type({}, "980__", {"a": "conferencepaper"}) == { "id": "publication-conferencepaper" } + assert resource_type({}, "980__", {"a": "report"}) == {"id": "publication-report"} + assert resource_type({}, "980__", {"a": "brochure"}) == { + "id": "publication-brochure" + } def test_resource_type_ignored_values():