diff --git a/epiuclid/serializers/pathway_mapper.py b/epiuclid/serializers/pathway_mapper.py index d50ce1ad..14ce91ef 100644 --- a/epiuclid/serializers/pathway_mapper.py +++ b/epiuclid/serializers/pathway_mapper.py @@ -120,13 +120,6 @@ class PathwayMapper: ) bundle.reference_substances.append(ref_sub) - sub = IUCLIDSubstanceData( - uuid=sub_uuid, - name=compound.name, - reference_substance_uuid=ref_sub_uuid, - ) - bundle.substances.append(sub) - if not export.compounds: return bundle @@ -145,6 +138,16 @@ class PathwayMapper: if not root_compound_pks: return bundle + for root_pk in root_compound_pks: + root_sub_uuid, root_ref_uuid = seen_compounds[root_pk] + bundle.substances.append( + IUCLIDSubstanceData( + uuid=root_sub_uuid, + name=compound_names[root_pk], + reference_substance_uuid=root_ref_uuid, + ) + ) + edge_templates: list[tuple[UUID, frozenset[int], tuple[int, ...], tuple[UUID, ...]]] = [] for edge in sorted(export.edges, key=lambda item: str(item.edge_uuid)): parent_compound_pks = sorted( diff --git a/epiuclid/tests/test_api.py b/epiuclid/tests/test_api.py index 84abdc25..8d00d8c8 100644 --- a/epiuclid/tests/test_api.py +++ b/epiuclid/tests/test_api.py @@ -70,8 +70,7 @@ class IUCLIDExportAPITest(TestCase): names = zf.namelist() self.assertIn("manifest.xml", names) i6d_files = [n for n in names if n.endswith(".i6d")] - # 2 substances + 2 ref substances + 1 ESR = 5 i6d files - self.assertEqual(len(i6d_files), 5) + self.assertEqual(len(i6d_files), 4) def test_anonymous_returns_401(self): self.client.logout() diff --git a/epiuclid/tests/test_i6z.py b/epiuclid/tests/test_i6z.py index f1fa5234..8cc1cd08 100644 --- a/epiuclid/tests/test_i6z.py +++ b/epiuclid/tests/test_i6z.py @@ -7,6 +7,11 @@ from uuid import uuid4 from django.test import SimpleTestCase, tag +from epapi.v1.interfaces.iuclid.dto import ( + PathwayCompoundDTO, + PathwayEdgeDTO, + PathwayExportDTO, +) from epiuclid.serializers.i6z import I6ZSerializer from epiuclid.serializers.pathway_mapper import ( IUCLIDDocumentBundle, @@ -14,9 +19,24 @@ from epiuclid.serializers.pathway_mapper import ( IUCLIDReferenceSubstanceData, IUCLIDSubstanceData, IUCLIDTransformationProductEntry, + PathwayMapper, ) +def _unlinked_documents(manifest_xml: str) -> list[tuple[str | None, str]]: + ns = "http://iuclid6.echa.europa.eu/namespaces/manifest/v1" + root = ET.fromstring(manifest_xml) + base = root.findtext(f"{{{ns}}}base-document-uuid") + linked_targets: set[str | None] = {base} + docs: dict[str, str | None] = {} + for doc in root.findall(f".//{{{ns}}}document"): + uuid = doc.findtext(f"{{{ns}}}uuid") + docs[uuid] = doc.findtext(f"{{{ns}}}type") + for link in doc.findall(f"{{{ns}}}links/{{{ns}}}link"): + linked_targets.add(link.findtext(f"{{{ns}}}ref-uuid")) + return [(doc_type, uuid) for uuid, doc_type in docs.items() if uuid not in linked_targets] + + def _make_bundle() -> IUCLIDDocumentBundle: ref_uuid = uuid4() sub_uuid = uuid4() @@ -197,3 +217,29 @@ class I6ZSerializerTest(SimpleTestCase): } self.assertIn(parent_ref_key, reference_links) self.assertIn(product_ref_key, reference_links) + + def test_multi_compound_pathway_has_no_unlinked_documents(self): + compounds = [ + PathwayCompoundDTO(pk=1, name="Root", smiles="c1ccccc1"), + PathwayCompoundDTO(pk=2, name="P1", smiles="CCO"), + PathwayCompoundDTO(pk=3, name="P2", smiles="CCN"), + PathwayCompoundDTO(pk=4, name="P3", smiles="CCC"), + ] + export = PathwayExportDTO( + pathway_uuid=uuid4(), + pathway_name="Regression Pathway", + compounds=compounds, + edges=[ + PathwayEdgeDTO(edge_uuid=uuid4(), start_compound_pks=[1], end_compound_pks=[2]), + PathwayEdgeDTO(edge_uuid=uuid4(), start_compound_pks=[1], end_compound_pks=[3]), + PathwayEdgeDTO(edge_uuid=uuid4(), start_compound_pks=[2], end_compound_pks=[4]), + ], + root_compound_pks=[1], + ) + bundle = PathwayMapper().map(export) + data = I6ZSerializer().serialize(bundle) + + with zipfile.ZipFile(io.BytesIO(data)) as zf: + manifest_xml = zf.read("manifest.xml").decode("utf-8") + + self.assertEqual(_unlinked_documents(manifest_xml), []) diff --git a/epiuclid/tests/test_pathway_mapper.py b/epiuclid/tests/test_pathway_mapper.py index 5c41c500..fe3d0f95 100644 --- a/epiuclid/tests/test_pathway_mapper.py +++ b/epiuclid/tests/test_pathway_mapper.py @@ -31,7 +31,7 @@ class PathwayMapperTest(SimpleTestCase): ) bundle = PathwayMapper().map(export) - self.assertEqual(len(bundle.substances), 2) + self.assertEqual(len(bundle.substances), 1) self.assertEqual(len(bundle.reference_substances), 2) self.assertEqual(len(bundle.endpoint_study_records), 1) @@ -49,8 +49,7 @@ class PathwayMapperTest(SimpleTestCase): ) bundle = PathwayMapper().map(export) - # 2 unique compounds -> 2 substances, 2 ref substances - self.assertEqual(len(bundle.substances), 2) + self.assertEqual(len(bundle.substances), 1) self.assertEqual(len(bundle.reference_substances), 2) # One endpoint study record per pathway self.assertEqual(len(bundle.endpoint_study_records), 1)