From e3b43fee8391d3819c264ea26c9cd9258aaf47a9 Mon Sep 17 00:00:00 2001 From: alexfurmenkov Date: Mon, 15 Dec 2025 17:24:08 +0100 Subject: [PATCH 1/5] 1325: extend define xml readers _get_metadata_representation --- .../services/define_xml/define_xml_reader_2_0.py | 3 +++ .../services/define_xml/define_xml_reader_2_1.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/cdisc_rules_engine/services/define_xml/define_xml_reader_2_0.py b/cdisc_rules_engine/services/define_xml/define_xml_reader_2_0.py index 080db2721..1f4949753 100644 --- a/cdisc_rules_engine/services/define_xml/define_xml_reader_2_0.py +++ b/cdisc_rules_engine/services/define_xml/define_xml_reader_2_0.py @@ -36,6 +36,9 @@ def _get_metadata_representation(self, metadata) -> dict: "define_dataset_structure": str(metadata.Structure), # v2.0 does not support is_non_standard. Default to blank "define_dataset_is_non_standard": "", + "define_dataset_has_no_data": bool( + getattr(metadata, "HasNoData", "").lower() == "yes" + ), } def get_extensible_codelist_mappings(self): diff --git a/cdisc_rules_engine/services/define_xml/define_xml_reader_2_1.py b/cdisc_rules_engine/services/define_xml/define_xml_reader_2_1.py index 0e0a86be6..81f7a1dee 100644 --- a/cdisc_rules_engine/services/define_xml/define_xml_reader_2_1.py +++ b/cdisc_rules_engine/services/define_xml/define_xml_reader_2_1.py @@ -38,6 +38,9 @@ def _get_metadata_representation(self, metadata) -> dict: "define_dataset_class": str(metadata.Class.Name), "define_dataset_structure": str(metadata.Structure), "define_dataset_is_non_standard": str(metadata.IsNonStandard or ""), + "define_dataset_has_no_data": bool( + getattr(metadata, "HasNoData", "").lower() == "yes" + ), } def get_ct_version(self): From 75d797161e1fe49af93630bbda081f4fe41842c0 Mon Sep 17 00:00:00 2001 From: alexfurmenkov Date: Mon, 15 Dec 2025 17:29:46 +0100 Subject: [PATCH 2/5] 1325: extend define xml readers _get_metadata_representation --- .../services/define_xml/define_xml_reader_2_0.py | 6 +++--- .../services/define_xml/define_xml_reader_2_1.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cdisc_rules_engine/services/define_xml/define_xml_reader_2_0.py b/cdisc_rules_engine/services/define_xml/define_xml_reader_2_0.py index 1f4949753..855473092 100644 --- a/cdisc_rules_engine/services/define_xml/define_xml_reader_2_0.py +++ b/cdisc_rules_engine/services/define_xml/define_xml_reader_2_0.py @@ -27,6 +27,8 @@ def _get_metadata_representation(self, metadata) -> dict: """ Returns metadata as dictionary. """ + has_no_data: str | None = getattr(metadata, "HasNoData", "") + has_no_data = has_no_data or "" return { "define_dataset_name": metadata.Name, "define_dataset_label": str(metadata.Description.TranslatedText[0]), @@ -36,9 +38,7 @@ def _get_metadata_representation(self, metadata) -> dict: "define_dataset_structure": str(metadata.Structure), # v2.0 does not support is_non_standard. Default to blank "define_dataset_is_non_standard": "", - "define_dataset_has_no_data": bool( - getattr(metadata, "HasNoData", "").lower() == "yes" - ), + "define_dataset_has_no_data": bool(has_no_data.lower() == "yes"), } def get_extensible_codelist_mappings(self): diff --git a/cdisc_rules_engine/services/define_xml/define_xml_reader_2_1.py b/cdisc_rules_engine/services/define_xml/define_xml_reader_2_1.py index 81f7a1dee..ddeac21df 100644 --- a/cdisc_rules_engine/services/define_xml/define_xml_reader_2_1.py +++ b/cdisc_rules_engine/services/define_xml/define_xml_reader_2_1.py @@ -30,6 +30,8 @@ def _get_metadata_representation(self, metadata) -> dict: """ Returns metadata as dictionary. """ + has_no_data: str | None = getattr(metadata, "HasNoData", "") + has_no_data = has_no_data or "" return { "define_dataset_name": metadata.Name, "define_dataset_label": str(metadata.Description.TranslatedText[0]), @@ -38,9 +40,7 @@ def _get_metadata_representation(self, metadata) -> dict: "define_dataset_class": str(metadata.Class.Name), "define_dataset_structure": str(metadata.Structure), "define_dataset_is_non_standard": str(metadata.IsNonStandard or ""), - "define_dataset_has_no_data": bool( - getattr(metadata, "HasNoData", "").lower() == "yes" - ), + "define_dataset_has_no_data": bool(has_no_data.lower() == "yes"), } def get_ct_version(self): From 2bc57cb4d424aab4b11bf62a0c54db3a83175a67 Mon Sep 17 00:00:00 2001 From: alexfurmenkov Date: Mon, 15 Dec 2025 17:32:53 +0100 Subject: [PATCH 3/5] 1325: adjust unit tests --- tests/unit/test_define_xml_reader.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/test_define_xml_reader.py b/tests/unit/test_define_xml_reader.py index 7a9fbbee7..d19336ce0 100644 --- a/tests/unit/test_define_xml_reader.py +++ b/tests/unit/test_define_xml_reader.py @@ -74,6 +74,7 @@ def test_read_define_xml(): "define_dataset_class", "define_dataset_structure", "define_dataset_is_non_standard", + "define_dataset_has_no_data", ] @@ -110,6 +111,7 @@ def test_extract_domain_metadata(filename): "TSVCDVER", ], "define_dataset_key_sequence": ["STUDYID", "TSPARMCD", "TSVAL", "TSSEQ"], + "define_dataset_has_no_data": False, } From dc6f2f2f61ffc2969d125db233984ff05a3f0aca Mon Sep 17 00:00:00 2001 From: alexfurmenkov Date: Wed, 24 Dec 2025 08:27:10 +0100 Subject: [PATCH 4/5] add unit test for NV domain with has_no_data in DefineXMLReader --- tests/unit/test_define_xml_reader.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/unit/test_define_xml_reader.py b/tests/unit/test_define_xml_reader.py index d19336ce0..d8f6205ec 100644 --- a/tests/unit/test_define_xml_reader.py +++ b/tests/unit/test_define_xml_reader.py @@ -372,3 +372,28 @@ def test_read_dictionary_version(dictionary_type, expected_version): reader = DefineXMLReaderFactory.from_file_contents(contents) version = reader.get_external_dictionary_version(dictionary_type) assert version == expected_version + + +@pytest.mark.parametrize( + "filename,has_no_data", + [ + ( + test_define_file_path, + True, + ), # NV domain in test_defineV22-SDTM.xml has def:HasNoData="Yes" + ], +) +def test_extract_domain_metadata_nv_has_no_data(filename, has_no_data): + """ + Unit test for DefineXMLReader.extract_domain_metadata for NV domain with has_no_data. + """ + with open(filename, "rb") as file: + contents: bytes = file.read() + reader = DefineXMLReaderFactory.from_file_contents(contents) + domain_metadata: dict = reader.extract_domain_metadata(domain_name="NV") + assert domain_metadata["define_dataset_has_no_data"] == has_no_data + assert domain_metadata["define_dataset_name"] == "NV" + assert domain_metadata["define_dataset_domain"] == "NV" + # Check that at least one expected variable is present + for v in ["NVSEQ", "NVTESTCD", "NVTEST"]: + assert v in domain_metadata["define_dataset_variables"] From fed1d378cf47767a7c6fe75f54bc84c1f45bea12 Mon Sep 17 00:00:00 2001 From: alexfurmenkov Date: Wed, 24 Dec 2025 08:51:45 +0100 Subject: [PATCH 5/5] add unit test for extract_dataset_metadata in DefineXMLReader for TS dataset --- tests/unit/test_define_xml_reader.py | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/unit/test_define_xml_reader.py b/tests/unit/test_define_xml_reader.py index d8f6205ec..bf7b16516 100644 --- a/tests/unit/test_define_xml_reader.py +++ b/tests/unit/test_define_xml_reader.py @@ -115,6 +115,43 @@ def test_extract_domain_metadata(filename): } +@pytest.mark.parametrize( + "filename", [(test_define_file_path), (test_define_2_0_file_path)] +) +def test_extract_dataset_metadata(filename): + """ + Unit test for DefineXMLReader.extract_dataset_metadata for TS dataset. + """ + with open(filename, "rb") as file: + contents: bytes = file.read() + reader = DefineXMLReaderFactory.from_file_contents(contents) + dataset_metadata: dict = reader.extract_dataset_metadata(dataset_name="TS") + assert dataset_metadata == { + "define_dataset_name": "TS", + "define_dataset_label": "Trial Summary", + "define_dataset_location": "ts.xml", + "define_dataset_domain": "TS", + "define_dataset_class": "TRIAL DESIGN", + "define_dataset_structure": "One record per trial summary parameter value", + "define_dataset_is_non_standard": "", + "define_dataset_variables": [ + "STUDYID", + "DOMAIN", + "TSSEQ", + "TSGRPID", + "TSPARMCD", + "TSPARM", + "TSVAL", + "TSVALNF", + "TSVALCD", + "TSVCDREF", + "TSVCDVER", + ], + "define_dataset_key_sequence": ["STUDYID", "TSPARMCD", "TSVAL", "TSSEQ"], + "define_dataset_has_no_data": False, + } + + @pytest.mark.parametrize( "filename", [(test_define_file_path), (test_define_2_0_file_path)] )