[Feature] Minimal IUCLID export (#338)

This is an initial implementation that creates a working minimal .i6z document.
It passes schema validation and can be imported into IUCLID.

Caveat:
IUCLID files target individual compounds.
Pathway is not actually covered by the format.

It can be added in either soil or water and soil OECD endpoints.
**I currently only implemented the soil endpoint for all data.**

This sort of works, and I can report all degradation products in a pathway (not a nice view, but we can report many transformation products and add a diagram attachment in the future).

Adding additional information is an absolute pain, as we need to explicitly map each type of information to the relevant OECD field.
I use the XSD scheme for validation, but unfortunately the IUCLID parser is not fully compliant and requires a specific order, etc.

The workflow is: finding the AI structure from the XSD scheme -> make the scheme validation pass -> upload to IUCLID to get obscure error messages -> guess what could be wrong -> repeat 💣

New specifications get released once per year, so we will have to update accordingly.
I believe that this should be a more expensive feature, as it requires significant effort to uphold.

Currently implemented for root compound only in SOIL:

- Soil Texture 2
- Soil Texture 1
- pH value
- Half-life per soil sample / scenario (mapped to disappearance; not sure about that).
- CEC
- Organic Matter (only Carbon)
- Moisture content
- Humidity

<img width="2123" alt="image.png" src="attachments/d29830e1-65ef-4136-8939-1825e0959c62">
<img width="2124" alt="image.png" src="attachments/ac9de2ac-bf68-4ba4-b40b-82f810a9de93">
<img width="2139" alt="image.png" src="attachments/5674c7e6-865e-420e-974a-6b825b331e6c">

Reviewed-on: enviPath/enviPy#338
Co-authored-by: Tobias O <tobias.olenyi@envipath.com>
Co-committed-by: Tobias O <tobias.olenyi@envipath.com>
This commit is contained in:
2026-04-07 19:46:12 +12:00
committed by jebus
parent f7c45b8015
commit d06bd0d4fd
49 changed files with 66402 additions and 1014 deletions

102
epiuclid/tests/factories.py Normal file
View File

@ -0,0 +1,102 @@
from __future__ import annotations
from uuid import uuid4
from epiuclid.serializers.pathway_mapper import (
HalfLifeEntry,
IUCLIDEndpointStudyRecordData,
IUCLIDReferenceSubstanceData,
IUCLIDSubstanceData,
IUCLIDTransformationProductEntry,
SoilPropertiesData,
)
def make_substance_data(**overrides) -> IUCLIDSubstanceData:
payload = {
"uuid": uuid4(),
"name": "Atrazine",
"reference_substance_uuid": uuid4(),
}
payload.update(overrides)
return IUCLIDSubstanceData(**payload)
def make_reference_substance_data(**overrides) -> IUCLIDReferenceSubstanceData:
payload = {
"uuid": uuid4(),
"name": "Atrazine",
"smiles": "CCNc1nc(Cl)nc(NC(C)C)n1",
"cas_number": "1912-24-9",
"ec_number": "217-617-8",
"iupac_name": "6-chloro-N2-ethyl-N4-isopropyl-1,3,5-triazine-2,4-diamine",
"molecular_formula": "C8H14ClN5",
"molecular_weight": 215.68,
"inchi": (
"InChI=1S/C8H14ClN5/c1-4-10-7-12-6(9)11-8(13-7)"
"14-5(2)3/h5H,4H2,1-3H3,(H2,10,11,12,13,14)"
),
"inchi_key": "MXWJVTOOROXGIU-UHFFFAOYSA-N",
}
payload.update(overrides)
return IUCLIDReferenceSubstanceData(**payload)
def make_half_life_entry(**overrides) -> HalfLifeEntry:
payload = {
"model": "SFO",
"dt50_start": 12.5,
"dt50_end": 15.0,
"unit": "d",
"source": "Model prediction",
"soil_no_code": None,
"temperature": None,
}
payload.update(overrides)
return HalfLifeEntry(**payload)
def make_transformation_entry(**overrides) -> IUCLIDTransformationProductEntry:
payload = {
"uuid": uuid4(),
"product_reference_uuid": uuid4(),
"parent_reference_uuids": [uuid4()],
"kinetic_formation_fraction": 0.42,
}
payload.update(overrides)
return IUCLIDTransformationProductEntry(**payload)
def make_soil_properties_data(**overrides) -> SoilPropertiesData:
payload = {
"soil_no_code": None,
"clay": 20.0,
"silt": 30.0,
"sand": 50.0,
"org_carbon": 1.5,
"ph_lower": 6.0,
"ph_upper": 7.0,
"ph_method": "CaCl2",
"cec": 12.0,
"moisture_content": 40.0,
"soil_type": "LOAM",
"soil_classification": "USDA",
}
payload.update(overrides)
return SoilPropertiesData(**payload)
def make_endpoint_study_record_data(**overrides) -> IUCLIDEndpointStudyRecordData:
payload = {
"uuid": uuid4(),
"substance_uuid": uuid4(),
"name": "Biodegradation study",
"half_lives": [],
"temperature": None,
"transformation_products": [],
"model_name_and_version": [],
"software_name_and_version": [],
"model_remarks": [],
}
payload.update(overrides)
return IUCLIDEndpointStudyRecordData(**payload)