CIRPASS reference structure v1.3.0 — API reference
Status: Final (Phase 8 finalisation, 2026-05-09). Generated
from the Pydantic models at
src/dppvalidator/models/cirpass/v1_3/.
The CIRPASS reference structure v1.3.0 is the message-level wire
format that the CIRPASS-2 project publishes alongside the EUDPP
ontology. dppvalidator's Pydantic models are the source of truth;
the JSON Schema bundled at
schemas/data/cirpass-reference-1.3.0.json
is derived from them via
tools/codegen/cirpass/derive_schema.py.
Reading guide
Root: ReferencePassport
The CIRPASS DPP reference structure root. Maps to eudpp:DPP
(P_DPP v1.9.1). Mirrors the v1.3.0 message tree-view shape: a
Product at the root + sibling fields for the DPP-level metadata.
dppvalidator.models.cirpass.v1_3.ReferencePassport
Bases: UNTPBaseModel
The CIRPASS DPP reference structure root.
Maps to eudpp:DPP (P_DPP v1.9.1). Mirrors the v1.3.0 message
tree-view shape: a Product at the root + sibling fields for the
DPP-level metadata.
Wire shape (minimal):
{"dppIdentifier": {...}, "product": <Product>,
"issuedAt": <IssuedAt>}
Cardinality:
dpp_identifier: required (1) — uniquely identifies this
DPP (distinct from the product's identifier).
product: required (1) — the Product the DPP describes.
issued_at: required (1) — when the DPP was issued.
effective_period: optional — explicit validity window.
related_actors: optional list of (actor, role) pairs.
actor_role_assignments: optional list of first-class
role-assignment relationships (v1.9.1 ACTOR addition).
composition: optional — the product's material composition.
substances_of_concern: optional (0..n).
lca: optional — Life-Cycle Assessment results.
connector_relations: optional (0..n) cross-module relations.
previous_dpp: optional URI of the DPP this one supersedes
(maps to eudpp:linkToPreviousDPP).
Source code in src/dppvalidator/models/cirpass/v1_3/passport.py
| Python |
|---|
| class ReferencePassport(UNTPBaseModel):
"""The CIRPASS DPP reference structure root.
Maps to ``eudpp:DPP`` (P_DPP v1.9.1). Mirrors the v1.3.0 message
tree-view shape: a Product at the root + sibling fields for the
DPP-level metadata.
Wire shape (minimal):
``{"dppIdentifier": {...}, "product": <Product>,
"issuedAt": <IssuedAt>}``
Cardinality:
- ``dpp_identifier``: required (1) — uniquely identifies *this*
DPP (distinct from the product's identifier).
- ``product``: required (1) — the Product the DPP describes.
- ``issued_at``: required (1) — when the DPP was issued.
- ``effective_period``: optional — explicit validity window.
- ``related_actors``: optional list of (actor, role) pairs.
- ``actor_role_assignments``: optional list of first-class
role-assignment relationships (v1.9.1 ACTOR addition).
- ``composition``: optional — the product's material composition.
- ``substances_of_concern``: optional (0..n).
- ``lca``: optional — Life-Cycle Assessment results.
- ``connector_relations``: optional (0..n) cross-module relations.
- ``previous_dpp``: optional URI of the DPP this one supersedes
(maps to ``eudpp:linkToPreviousDPP``).
"""
_jsonld_type: ClassVar[list[str]] = ["DigitalProductPassport", EUDPPClass.DPP.value]
dpp_identifier: Identifier = Field(
...,
alias="dppIdentifier",
description="Unique identifier for this DPP instance (``eudpp:uniqueDPPID``).",
)
product: Product = Field(
...,
description="The product this DPP describes.",
)
issued_at: IssuedAt = Field(
...,
alias="issuedAt",
description="When this DPP was issued.",
)
effective_period: EffectivePeriod | None = Field(
default=None,
alias="effectivePeriod",
description="Validity window during which this DPP applies.",
)
related_actors: list[ActorRole] | None = Field(
default=None,
alias="relatedActors",
description=(
"Actors associated with this DPP and their roles "
"(manufacturer, importer, recycler, etc.)."
),
)
actor_role_assignments: list[ActorRoleAssignment] | None = Field(
default=None,
alias="actorRoleAssignments",
description=(
"First-class actor-plays-role-in-context relationships "
"(v1.9.1 ACTOR module). Use this when an assignment "
"carries its own metadata (temporal scope, conferring "
"authority, supporting documentation)."
),
)
composition: Composition | None = Field(
default=None,
description="Material composition of the product.",
)
substances_of_concern: list[SubstanceOfConcern] | None = Field(
default=None,
alias="substancesOfConcern",
description="REACH / SVHC-tracked substances present in the product.",
)
lca: LifeCycleAssessment | None = Field(
default=None,
description="Life-Cycle Assessment / EPD results.",
)
connector_relations: list[ConnectorRelation] | None = Field(
default=None,
alias="connectorRelations",
description="Cross-module typed relations (CON v1.9.1 predicates).",
)
previous_dpp: str | None = Field(
default=None,
alias="previousDpp",
description=(
"URI of the DPP this one supersedes "
"(``eudpp:linkToPreviousDPP``). Enables version chains."
),
)
|
options:
show_source: false
show_bases: false
Product / Identifier / Classification
dppvalidator.models.cirpass.v1_3.Product
Bases: UNTPBaseModel
A physical product placed on the EU market.
Maps to eudpp:Product (P_DPP v1.9.1). Carries the product's
primary identifier, optional commodity classification, and
multilingual product names.
Wire shape (minimal):
{"productIdentifier": {...}, "productName": [{"value": "...", "language": "en"}]}
Cardinality:
productIdentifier: required (1).
productName: required (≥1) — at least one language must be
provided. Multiple are encouraged for ESPR multilingual reach.
description: optional (0..1 list of LocalisedText).
commodityCode: optional (0..n list of ClassificationCode).
isComponentOf / isSparePartOf: optional self-references
modelling the v1.9.1 transitive product hierarchy.
Source code in src/dppvalidator/models/cirpass/v1_3/product.py
| Python |
|---|
| class Product(UNTPBaseModel):
"""A physical product placed on the EU market.
Maps to ``eudpp:Product`` (P_DPP v1.9.1). Carries the product's
primary identifier, optional commodity classification, and
multilingual product names.
Wire shape (minimal):
``{"productIdentifier": {...}, "productName": [{"value": "...", "language": "en"}]}``
Cardinality:
- ``productIdentifier``: required (1).
- ``productName``: required (≥1) — at least one language must be
provided. Multiple are encouraged for ESPR multilingual reach.
- ``description``: optional (0..1 list of LocalisedText).
- ``commodityCode``: optional (0..n list of ClassificationCode).
- ``isComponentOf`` / ``isSparePartOf``: optional self-references
modelling the v1.9.1 transitive product hierarchy.
"""
_jsonld_type: ClassVar[list[str]] = ["Product", EUDPPClass.PRODUCT.value]
product_identifier: Identifier = Field(
...,
alias="productIdentifier",
description="Primary product identifier (typically a GTIN or SPC URI).",
)
product_name: list[LocalisedText] = Field(
...,
alias="productName",
min_length=1,
description="Product name(s), one entry per supported language.",
)
description: list[LocalisedText] | None = Field(
default=None,
description="Product description(s), one entry per language.",
)
commodity_code: list[ClassificationCode] | None = Field(
default=None,
alias="commodityCode",
description="Commodity / taxonomy classifications for this product.",
)
is_component_of: list[str] | None = Field(
default=None,
alias="isComponentOf",
description="URIs of products this product is a component of (transitive).",
)
is_spare_part_of: list[str] | None = Field(
default=None,
alias="isSparePartOf",
description="URIs of products this product is a spare part of.",
)
|
options:
show_source: false
show_bases: false
dppvalidator.models.cirpass.v1_3.Identifier
Bases: UNTPBaseModel
A typed product or DPP identifier.
Wire shape
{"value": "01234567890128", "scheme": "https://gs1.org/voc/", "schemeName": "GS1 GTIN"}
value carries the identifier itself; scheme is the URI of
the issuing authority's identifier register; schemeName is a
short human label (e.g. GS1 GTIN, ISO/IEC 15459).
The scheme URI is what disambiguates collisions between identifier
spaces (a 13-digit string can be a GTIN-13, an EAN-13, or
something else entirely depending on the issuing authority).
Source code in src/dppvalidator/models/cirpass/v1_3/product.py
| Python |
|---|
| class Identifier(UNTPBaseModel):
"""A typed product or DPP identifier.
Wire shape:
``{"value": "01234567890128", "scheme": "https://gs1.org/voc/", "schemeName": "GS1 GTIN"}``
``value`` carries the identifier itself; ``scheme`` is the URI of
the issuing authority's identifier register; ``schemeName`` is a
short human label (e.g. ``GS1 GTIN``, ``ISO/IEC 15459``).
The scheme URI is what disambiguates collisions between identifier
spaces (a 13-digit string can be a GTIN-13, an EAN-13, or
something else entirely depending on the issuing authority).
"""
_jsonld_type: ClassVar[list[str]] = ["Identifier"]
value: str = Field(..., min_length=1, description="The identifier value.")
scheme: str = Field(
...,
description=(
"URI of the identifier scheme (the issuing authority's "
"register, e.g. ``https://gs1.org/voc/``)."
),
)
scheme_name: str | None = Field(
default=None,
alias="schemeName",
description="Human-readable scheme name (``GS1 GTIN``, ``ISO/IEC 15459``).",
)
@field_validator("scheme")
@classmethod
def _scheme_is_uri(cls, value: str) -> str:
if not (value.startswith("http://") or value.startswith("https://")):
msg = (
f"Identifier.scheme must be an http(s) URI; got {value!r}. "
f"The scheme URI is what disambiguates identifier spaces — "
f"a bare label (e.g. ``GTIN``) is not enough."
)
raise ValueError(msg)
return value
|
options:
show_source: false
show_bases: false
dppvalidator.models.cirpass.v1_3.ClassificationCode
Bases: UNTPBaseModel
A taxonomy classification (commodity code, HS code, etc.).
Wire shape
{"code": "61091000", "scheme": "https://www.wcoomd.org/.../HS",
"name": [{"value": "T-shirts...", "language": "en"}]}
Maps to eudpp:ClassificationCode (P_DPP v1.9.1). The CIRPASS
spec uses this for HS / TARIC / commodity codes; the scheme
URI tells consumers which taxonomy the code belongs to.
Source code in src/dppvalidator/models/cirpass/v1_3/product.py
| Python |
|---|
| class ClassificationCode(UNTPBaseModel):
"""A taxonomy classification (commodity code, HS code, etc.).
Wire shape:
``{"code": "61091000", "scheme": "https://www.wcoomd.org/.../HS",
"name": [{"value": "T-shirts...", "language": "en"}]}``
Maps to ``eudpp:ClassificationCode`` (P_DPP v1.9.1). The CIRPASS
spec uses this for HS / TARIC / commodity codes; the ``scheme``
URI tells consumers which taxonomy the code belongs to.
"""
_jsonld_type: ClassVar[list[str]] = ["ClassificationCode"]
code: str = Field(..., min_length=1, description="The classification code value.")
scheme: str = Field(
...,
description="URI of the classification scheme (HS, TARIC, CPV, …).",
)
name: list[LocalisedText] | None = Field(
default=None,
description="Human-readable description of the code, in one or more languages.",
)
@field_validator("scheme")
@classmethod
def _scheme_is_uri(cls, value: str) -> str:
if not (value.startswith("http://") or value.startswith("https://")):
msg = f"ClassificationCode.scheme must be an http(s) URI; got {value!r}."
raise ValueError(msg)
return value
|
options:
show_source: false
show_bases: false
Actor / Role
dppvalidator.models.cirpass.v1_3.Actor
Bases: UNTPBaseModel
An economic operator, regulator, or other party.
Maps to eudpp:Actor (ACTOR v1.9.1). Carries the actor's
primary identifier, a multilingual name, and optional contact /
address fields.
Wire shape (minimal):
{"actorIdentifier": {...}, "actorName": [...]}
Source code in src/dppvalidator/models/cirpass/v1_3/actor.py
| Python |
|---|
| class Actor(UNTPBaseModel):
"""An economic operator, regulator, or other party.
Maps to ``eudpp:Actor`` (ACTOR v1.9.1). Carries the actor's
primary identifier, a multilingual name, and optional contact /
address fields.
Wire shape (minimal):
``{"actorIdentifier": {...}, "actorName": [...]}``
"""
_jsonld_type: ClassVar[list[str]] = ["Actor"]
actor_identifier: Identifier = Field(
...,
alias="actorIdentifier",
description="Primary actor identifier (LEI, EUID, EORI, …).",
)
actor_name: list[LocalisedText] = Field(
...,
alias="actorName",
min_length=1,
description="Actor's legal / trade name(s), one per language.",
)
registered_trade_name: list[LocalisedText] | None = Field(
default=None,
alias="registeredTradeName",
description="Registered trade-name(s) where distinct from the legal name.",
)
registered_trademark: list[LocalisedText] | None = Field(
default=None,
alias="registeredTrademark",
description="Registered trademark(s) associated with the actor.",
)
|
options:
show_source: false
show_bases: false
dppvalidator.models.cirpass.v1_3.Facility
Bases: UNTPBaseModel
A physical site where production / processing occurs.
Maps to eudpp:Facility (ACTOR v1.9.1 — relocated from P_DPP in
the v1.9.1 spec rewrite). The CIRPASS message references a Facility
via the actor's usesFacility relation; the facility itself
carries an identifier scheme (so e.g. a permit ID and an ECEFACT
ID for the same facility don't collide).
Source code in src/dppvalidator/models/cirpass/v1_3/actor.py
| Python |
|---|
| class Facility(UNTPBaseModel):
"""A physical site where production / processing occurs.
Maps to ``eudpp:Facility`` (ACTOR v1.9.1 — relocated from P_DPP in
the v1.9.1 spec rewrite). The CIRPASS message references a Facility
via the actor's ``usesFacility`` relation; the facility itself
carries an identifier scheme (so e.g. a permit ID and an ECEFACT
ID for the same facility don't collide).
"""
_jsonld_type: ClassVar[list[str]] = ["Facility"]
facility_identifier: Identifier = Field(
...,
alias="facilityIdentifier",
description="Primary facility identifier with scheme.",
)
facility_name: list[LocalisedText] = Field(
...,
alias="facilityName",
min_length=1,
description="Facility name(s), one per language.",
)
|
options:
show_source: false
show_bases: false
dppvalidator.models.cirpass.v1_3.ActorRole
Bases: UNTPBaseModel
An actor playing a specific role on this DPP.
Wire shape
{"actor": <Actor>, "role": "eudpp:ManufacturerRole"}
The role field accepts the compact eudpp: IRI of any role
class — both the v1.9.1 super-categories
(EconomicOperatorRole, CircularEconomyRole, etc.) and the
finer-grained ESPR-derived roles
(ManufacturerRole, RecyclerRole, etc.) that
:class:EUDPPRoleClass exposes for back-compat.
Source code in src/dppvalidator/models/cirpass/v1_3/actor.py
| Python |
|---|
| class ActorRole(UNTPBaseModel):
"""An actor playing a specific role on this DPP.
Wire shape:
``{"actor": <Actor>, "role": "eudpp:ManufacturerRole"}``
The ``role`` field accepts the compact ``eudpp:`` IRI of any role
class — both the v1.9.1 super-categories
(``EconomicOperatorRole``, ``CircularEconomyRole``, etc.) and the
finer-grained ESPR-derived roles
(``ManufacturerRole``, ``RecyclerRole``, etc.) that
:class:`EUDPPRoleClass` exposes for back-compat.
"""
_jsonld_type: ClassVar[list[str]] = ["ActorRole"]
actor: Actor = Field(..., description="The actor playing the role.")
role: str = Field(
...,
description=(
"Compact ``eudpp:`` IRI of the role class. See "
":class:`dppvalidator.vocabularies.eudpp_actors.EUDPPRoleClass` "
"for the canonical set."
),
)
@property
def role_enum(self) -> EUDPPRoleClass | None:
"""Return the :class:`EUDPPRoleClass` member if ``role`` matches one.
Returns ``None`` for roles outside the registered set
(downstream taxonomies that extend the EUDPP role hierarchy
with their own classes are still legal — :class:`ActorRole`
carries the IRI string, not an enforced enum).
"""
try:
return EUDPPRoleClass(self.role)
except ValueError:
return None
|
role_enum
property
Return the :class:EUDPPRoleClass member if role matches one.
Returns None for roles outside the registered set
(downstream taxonomies that extend the EUDPP role hierarchy
with their own classes are still legal — :class:ActorRole
carries the IRI string, not an enforced enum).
options:
show_source: false
show_bases: false
dppvalidator.models.cirpass.v1_3.ActorRoleAssignment
Bases: UNTPBaseModel
First-class actor-plays-role-in-context relationship (ACTOR v1.9.1).
+1.9.1: ACTOR v1.9.1 introduced eudpp:ActorRoleAssignment as a
first-class entity (rather than a binary edge) so that role
assignments can carry their own metadata — temporal scoping,
authority that conferred the role, supporting documentation, etc.
Wire shape
{"actor": <Actor>, "role": "eudpp:...",
"validFrom": "2026-01-01T00:00:00Z",
"validTo": "2031-12-31T23:59:59Z"}
Source code in src/dppvalidator/models/cirpass/v1_3/actor.py
| Python |
|---|
| class ActorRoleAssignment(UNTPBaseModel):
"""First-class actor-plays-role-in-context relationship (ACTOR v1.9.1).
+1.9.1: ACTOR v1.9.1 introduced ``eudpp:ActorRoleAssignment`` as a
first-class entity (rather than a binary edge) so that role
assignments can carry their own metadata — temporal scoping,
authority that conferred the role, supporting documentation, etc.
Wire shape:
``{"actor": <Actor>, "role": "eudpp:...",
"validFrom": "2026-01-01T00:00:00Z",
"validTo": "2031-12-31T23:59:59Z"}``
"""
_jsonld_type: ClassVar[list[str]] = ["ActorRoleAssignment"]
actor: Actor = Field(..., description="The actor receiving the role.")
role: str = Field(
...,
description="Compact ``eudpp:`` IRI of the role class assigned.",
)
valid_from: str | None = Field(
default=None,
alias="validFrom",
description=(
"ISO 8601 UTC datetime from which the assignment is "
"effective (``eudpp:assignmentValidFrom``)."
),
)
valid_to: str | None = Field(
default=None,
alias="validTo",
description=(
"ISO 8601 UTC datetime after which the assignment expires "
"(``eudpp:assignmentValidTo``). Absent ⇒ open-ended."
),
)
|
options:
show_source: false
show_bases: false
Material / Composition
dppvalidator.models.cirpass.v1_3.Material
Bases: UNTPBaseModel
A constituent material of a product.
Wire shape
{"materialName": [...], "materialType": "CO",
"originCountry": "DE", "massFraction": 0.62,
"isRecycled": true}
Cardinality:
materialName: required (≥1) — multilingual name of the
material.
materialType: optional ISO 2076 fibre / material code
(CO = Cotton, EL = Elastane, …). Bare-string per ISO
convention; not wrapped in :class:LocalisedText.
originCountry: optional ISO-3166-1 alpha-2 country code.
massFraction: optional Decimal in [0, 1]; sum of all
massFraction values across a product's materials should be
≤ 1.0 (validated cross-component, not on a single Material).
isRecycled: optional flag; True ⇒ material is reclaimed /
post-consumer.
Source code in src/dppvalidator/models/cirpass/v1_3/material.py
| Python |
|---|
| class Material(UNTPBaseModel):
"""A constituent material of a product.
Wire shape:
``{"materialName": [...], "materialType": "CO",
"originCountry": "DE", "massFraction": 0.62,
"isRecycled": true}``
Cardinality:
- ``materialName``: required (≥1) — multilingual name of the
material.
- ``materialType``: optional ISO 2076 fibre / material code
(``CO`` = Cotton, ``EL`` = Elastane, …). Bare-string per ISO
convention; not wrapped in :class:`LocalisedText`.
- ``originCountry``: optional ISO-3166-1 alpha-2 country code.
- ``massFraction``: optional Decimal in [0, 1]; sum of all
``massFraction`` values across a product's materials should be
≤ 1.0 (validated cross-component, not on a single Material).
- ``isRecycled``: optional flag; True ⇒ material is reclaimed /
post-consumer.
"""
_jsonld_type: ClassVar[list[str]] = ["Material"]
material_name: list[LocalisedText] = Field(
...,
alias="materialName",
min_length=1,
description="Material name(s), one per language.",
)
material_type: str | None = Field(
default=None,
alias="materialType",
description="ISO 2076 textile-fibre code or equivalent material code.",
)
origin_country: str | None = Field(
default=None,
alias="originCountry",
description="ISO-3166-1 alpha-2 country code of material origin.",
)
mass_fraction: Decimal | None = Field(
default=None,
alias="massFraction",
ge=Decimal("0"),
le=Decimal("1"),
description="Mass fraction in [0, 1]; recycle/composition share.",
)
is_recycled: bool | None = Field(
default=None,
alias="isRecycled",
description="True if the material is reclaimed / post-consumer recycled.",
)
@field_validator("material_type")
@classmethod
def _validate_material_type(cls, value: str | None) -> str | None:
if value is None:
return value
if not _ISO_2076_RE.match(value):
msg = (
f"Material.materialType={value!r} is not a 2-letter ISO "
f"2076 code (e.g. ``CO`` for Cotton). Use the bare ISO "
f"code, not the human-readable name."
)
raise ValueError(msg)
return value
@field_validator("origin_country")
@classmethod
def _validate_origin_country(cls, value: str | None) -> str | None:
if value is None:
return value
if not _ISO_3166_ALPHA2_RE.match(value):
msg = f"Material.originCountry={value!r} is not a 2-letter ISO-3166-1 alpha-2 code."
raise ValueError(msg)
return value
|
options:
show_source: false
show_bases: false
dppvalidator.models.cirpass.v1_3.Composition
Bases: UNTPBaseModel
The full material composition of a product.
Wire shape
{"materials": [<Material>, ...]}
The mass-fraction sum invariant is enforced at this level: across
all entries with non-null massFraction, the sum must be ≤ 1.0
(slightly less than 1 is valid — non-mass-bearing constituents
don't carry a fraction).
Source code in src/dppvalidator/models/cirpass/v1_3/material.py
| Python |
|---|
| class Composition(UNTPBaseModel):
"""The full material composition of a product.
Wire shape:
``{"materials": [<Material>, ...]}``
The mass-fraction sum invariant is enforced at this level: across
all entries with non-null ``massFraction``, the sum must be ≤ 1.0
(slightly less than 1 is valid — non-mass-bearing constituents
don't carry a fraction).
"""
_jsonld_type: ClassVar[list[str]] = ["Composition"]
materials: list[Material] = Field(
...,
min_length=1,
description="The materials that constitute the product.",
)
@model_validator(mode="after")
def _mass_fractions_sum_within_one(self) -> Composition:
total = sum(
(m.mass_fraction for m in self.materials if m.mass_fraction is not None),
start=Decimal("0"),
)
# Allow tiny floating-error tolerance — the spec is a sum of
# Decimals so exact 1.0 is normal, but 0.9999999 rounding is
# also seen in upstream feeds.
if total > Decimal("1.0001"):
msg = (
f"Composition.materials mass fractions sum to {total} "
f"(> 1.0). The sum across all materials with a non-null "
f"massFraction must be ≤ 1.0."
)
raise ValueError(msg)
return self
|
options:
show_source: false
show_bases: false
Substances of Concern
dppvalidator.models.cirpass.v1_3.SubstanceOfConcern
Bases: UNTPBaseModel
A REACH / SVHC-tracked substance present in a product.
Maps to eudpp:SubstanceOfConcern (SOC v1.9.1). Carries IUPAC /
CAS / EC identifiers, hazard classifications, and one or more
concentration measurements.
Wire shape (minimal):
{"nameIUPAC": "...", "concentrations": [<Concentration>, ...]}
Cardinality:
- At least one of
nameIUPAC / nameCAS / numberCAS /
numberEC must be present (substance must be identifiable).
concentrations: required (≥1).
hazards: optional (0..n).
Source code in src/dppvalidator/models/cirpass/v1_3/substances.py
| Python |
|---|
| class SubstanceOfConcern(UNTPBaseModel):
"""A REACH / SVHC-tracked substance present in a product.
Maps to ``eudpp:SubstanceOfConcern`` (SOC v1.9.1). Carries IUPAC /
CAS / EC identifiers, hazard classifications, and one or more
concentration measurements.
Wire shape (minimal):
``{"nameIUPAC": "...", "concentrations": [<Concentration>, ...]}``
Cardinality:
- At least *one* of ``nameIUPAC`` / ``nameCAS`` / ``numberCAS`` /
``numberEC`` must be present (substance must be identifiable).
- ``concentrations``: required (≥1).
- ``hazards``: optional (0..n).
"""
_jsonld_type: ClassVar[list[str]] = ["SubstanceOfConcern"]
name_iupac: str | None = Field(
default=None,
alias="nameIUPAC",
description="IUPAC systematic name.",
)
name_cas: str | None = Field(
default=None,
alias="nameCAS",
description="CAS-registered name.",
)
number_cas: str | None = Field(
default=None,
alias="numberCAS",
description="CAS Registry Number (e.g. ``71-43-2``).",
)
number_ec: str | None = Field(
default=None,
alias="numberEC",
description="EC Number (e.g. ``200-753-7``).",
)
trade_name: list[LocalisedText] | None = Field(
default=None,
alias="tradeName",
description="Trade name(s) under which the substance is marketed.",
)
concentrations: list[Concentration] = Field(
...,
min_length=1,
description="One or more concentration measurements for this substance.",
)
hazards: list[HazardClassification] | None = Field(
default=None,
description="Optional hazard classifications for this substance.",
)
@field_validator("number_cas")
@classmethod
def _validate_cas(cls, value: str | None) -> str | None:
if value is not None and not _CAS_RE.match(value):
msg = (
f"SubstanceOfConcern.numberCAS={value!r} is not a "
f"recognised CAS Registry Number format "
f"(``<digits>-<2 digits>-<check digit>``, e.g. ``71-43-2``)."
)
raise ValueError(msg)
return value
@field_validator("number_ec")
@classmethod
def _validate_ec(cls, value: str | None) -> str | None:
if value is not None and not _EC_RE.match(value):
msg = (
f"SubstanceOfConcern.numberEC={value!r} is not a "
f"recognised EC Number format "
f"(``<3 digits>-<3 digits>-<check digit>``, e.g. ``200-753-7``)."
)
raise ValueError(msg)
return value
def is_identified(self) -> bool:
"""True if at least one identifier (IUPAC / CAS / EC) is present."""
return any(
v is not None for v in (self.name_iupac, self.name_cas, self.number_cas, self.number_ec)
)
|
is_identified()
True if at least one identifier (IUPAC / CAS / EC) is present.
Source code in src/dppvalidator/models/cirpass/v1_3/substances.py
| Python |
|---|
| def is_identified(self) -> bool:
"""True if at least one identifier (IUPAC / CAS / EC) is present."""
return any(
v is not None for v in (self.name_iupac, self.name_cas, self.number_cas, self.number_ec)
)
|
options:
show_source: false
show_bases: false
dppvalidator.models.cirpass.v1_3.Concentration
Bases: UNTPBaseModel
The concentration of a substance of concern within a product / component.
Wire shape
{"value": 0.0008, "unit": "mass_fraction",
"lifecycleStage": "in_product"}
Maps to eudpp:Concentration (SOC v1.9.1). The value is a
Decimal in [0, 1] when unit is mass_fraction; for other
units (e.g. mg_per_kg) the bound is on the unit's conventional
range.
Source code in src/dppvalidator/models/cirpass/v1_3/substances.py
| Python |
|---|
| class Concentration(UNTPBaseModel):
"""The concentration of a substance of concern within a product / component.
Wire shape:
``{"value": 0.0008, "unit": "mass_fraction",
"lifecycleStage": "in_product"}``
Maps to ``eudpp:Concentration`` (SOC v1.9.1). The ``value`` is a
Decimal in [0, 1] when ``unit`` is ``mass_fraction``; for other
units (e.g. ``mg_per_kg``) the bound is on the unit's conventional
range.
"""
_jsonld_type: ClassVar[list[str]] = ["Concentration"]
value: Decimal = Field(
...,
ge=Decimal("0"),
description="Concentration value in the unit identified by ``unit``.",
)
unit: str = Field(
...,
description="Unit of measurement (``mass_fraction``, ``mg_per_kg``, …).",
)
lifecycle_stage: LifeCycleStage | None = Field(
default=None,
alias="lifecycleStage",
description="Lifecycle stage at which this concentration applies.",
)
@field_validator("value")
@classmethod
def _bound_when_mass_fraction(cls, value: Decimal) -> Decimal:
# Mass-fraction-specific upper bound (1.0) is enforced by a
# cross-field validator below; this validator just ensures
# the value is non-negative regardless of unit.
return value
|
options:
show_source: false
show_bases: false
dppvalidator.models.cirpass.v1_3.HazardClassification
Bases: UNTPBaseModel
A single hazard classification per CLP / Regulation (EC) 1272/2008.
Wire shape
{"category": "carcinogenicity",
"statement": [{"value": "May cause cancer.", "language": "en"}]}
The category field accepts any value from
:class:dppvalidator.vocabularies.eudpp_substances.HazardCategory;
new categories added in a future SOC release land in that enum,
not here.
Source code in src/dppvalidator/models/cirpass/v1_3/substances.py
| Python |
|---|
| class HazardClassification(UNTPBaseModel):
"""A single hazard classification per CLP / Regulation (EC) 1272/2008.
Wire shape:
``{"category": "carcinogenicity",
"statement": [{"value": "May cause cancer.", "language": "en"}]}``
The ``category`` field accepts any value from
:class:`dppvalidator.vocabularies.eudpp_substances.HazardCategory`;
new categories added in a future SOC release land in that enum,
not here.
"""
_jsonld_type: ClassVar[list[str]] = ["HazardClassification"]
category: HazardCategory = Field(
...,
description=(
"CLP / SVHC hazard category (carcinogenicity, mutagenicity, "
"reproductive toxicity, PBT, vPvB, PMT, vPvM, …)."
),
)
statement: list[LocalisedText] | None = Field(
default=None,
description=("Optional H-statement text(s) describing the hazard, one per language."),
)
|
options:
show_source: false
show_bases: false
Life-Cycle Assessment
dppvalidator.models.cirpass.v1_3.LifeCycleAssessment
Bases: UNTPBaseModel
A complete LCA / EPD attached to a product.
Maps to eudpp:LCAStudy (LCA v1.9.4-Maki). Carries one or more
impact results plus optional methodology metadata. Phase 4 wires
SHACL validation of the impact-result set against the bundled
LCA shape graph.
Wire shape (minimal):
{"results": [<ImpactResult>, ...]}
Cardinality:
results: required (≥1).
methodology: optional — short label (PEF 3.1,
EN 15804+A2, …) or compact IRI of an
:class:dppvalidator.vocabularies.eudpp_lca.LCAClass
LCIAMethodology instance.
referencePeriod: optional — when the LCA result was
computed (production batch, study window, etc.).
Source code in src/dppvalidator/models/cirpass/v1_3/lca.py
| Python |
|---|
| class LifeCycleAssessment(UNTPBaseModel):
"""A complete LCA / EPD attached to a product.
Maps to ``eudpp:LCAStudy`` (LCA v1.9.4-Maki). Carries one or more
impact results plus optional methodology metadata. Phase 4 wires
SHACL validation of the impact-result set against the bundled
LCA shape graph.
Wire shape (minimal):
``{"results": [<ImpactResult>, ...]}``
Cardinality:
- ``results``: required (≥1).
- ``methodology``: optional — short label (``PEF 3.1``,
``EN 15804+A2``, …) or compact IRI of an
:class:`dppvalidator.vocabularies.eudpp_lca.LCAClass`
``LCIAMethodology`` instance.
- ``referencePeriod``: optional — when the LCA result was
computed (production batch, study window, etc.).
"""
_jsonld_type: ClassVar[list[str]] = ["LifeCycleAssessment"]
results: list[ImpactResult] = Field(
...,
min_length=1,
description="The quantified impact results.",
)
methodology: str | None = Field(
default=None,
description=(
"LCA methodology label or compact IRI (``PEF 3.1``, ``eudpp:LCIAMethodology``, …)."
),
)
reference_period: EffectivePeriod | None = Field(
default=None,
alias="referencePeriod",
description="The temporal scope of the LCA results.",
)
|
options:
show_source: false
show_bases: false
dppvalidator.models.cirpass.v1_3.ImpactResult
Bases: UNTPBaseModel
A single quantified environmental impact result.
Wire shape
{"impactCategory": <ImpactCategoryReference>,
"value": 12.34, "unit": "kg_CO2_eq"}
Pairs an impact-category reference with a numeric value + unit.
The unit field is a free-string for now (PEF 3.1 conventions
use shorthand like kg_CO2_eq, CTUe); UNECE Rec20 / QUDT
normalisation is enforced at the validator layer (Phase 4 LCS
rule pack).
Source code in src/dppvalidator/models/cirpass/v1_3/lca.py
| Python |
|---|
| class ImpactResult(UNTPBaseModel):
"""A single quantified environmental impact result.
Wire shape:
``{"impactCategory": <ImpactCategoryReference>,
"value": 12.34, "unit": "kg_CO2_eq"}``
Pairs an impact-category reference with a numeric value + unit.
The ``unit`` field is a free-string for now (PEF 3.1 conventions
use shorthand like ``kg_CO2_eq``, ``CTUe``); UNECE Rec20 / QUDT
normalisation is enforced at the validator layer (Phase 4 LCS
rule pack).
"""
_jsonld_type: ClassVar[list[str]] = ["ImpactResult"]
impact_category: ImpactCategoryReference = Field(
...,
alias="impactCategory",
description="The PEF / EN 15804 impact category quantified.",
)
value: Decimal = Field(..., description="The numeric impact value.")
unit: str = Field(
...,
description=(
"Unit of measurement (``kg_CO2_eq``, ``CTUe``, …). PEF 3.1 "
"conventions; Phase 4 enforces UNECE Rec20 / QUDT alignment."
),
)
|
options:
show_source: false
show_bases: false
dppvalidator.models.cirpass.v1_3.ImpactCategoryReference
Bases: UNTPBaseModel
A reference to a PEF / EN 15804 impact category.
Wire shape
{"category": "eudpp:climateChange",
"name": [{"value": "Climate change", "language": "en"}]}
The category field is a compact eudpp: IRI from
:class:dppvalidator.vocabularies.eudpp_lca.LCIAImpactCategory
(the v1.9.4-Maki canonical set: 16 PEF/EN15804+A2 individuals).
Legacy v2.0 lca: IRIs are tolerated here for back-compat with
UNTP-sourced payloads — the Phase 5 mapping shim translates them.
Source code in src/dppvalidator/models/cirpass/v1_3/lca.py
| Python |
|---|
| class ImpactCategoryReference(UNTPBaseModel):
"""A reference to a PEF / EN 15804 impact category.
Wire shape:
``{"category": "eudpp:climateChange",
"name": [{"value": "Climate change", "language": "en"}]}``
The ``category`` field is a compact ``eudpp:`` IRI from
:class:`dppvalidator.vocabularies.eudpp_lca.LCIAImpactCategory`
(the v1.9.4-Maki canonical set: 16 PEF/EN15804+A2 individuals).
Legacy v2.0 ``lca:`` IRIs are tolerated here for back-compat with
UNTP-sourced payloads — the Phase 5 mapping shim translates them.
"""
_jsonld_type: ClassVar[list[str]] = ["ImpactCategoryReference"]
category: str = Field(
...,
description=(
"Compact ``eudpp:`` IRI of the impact category (canonical "
"v1.9.4-Maki). Legacy ``lca:`` IRIs accepted for back-compat."
),
)
name: list[LocalisedText] | None = Field(
default=None,
description="Human-readable display name(s) of the impact category.",
)
@property
def category_enum(self) -> LCIAImpactCategory | None:
"""Return the :class:`LCIAImpactCategory` member if ``category`` matches one.
Returns ``None`` for category IRIs outside the canonical
v1.9.4-Maki set (e.g. legacy ``lca:`` IRIs, or downstream
taxonomies that extend the impact-category hierarchy).
"""
try:
return LCIAImpactCategory(self.category)
except ValueError:
return None
|
category_enum
property
Return the :class:LCIAImpactCategory member if category matches one.
Returns None for category IRIs outside the canonical
v1.9.4-Maki set (e.g. legacy lca: IRIs, or downstream
taxonomies that extend the impact-category hierarchy).
options:
show_source: false
show_bases: false
Connector relations
dppvalidator.models.cirpass.v1_3.ConnectorRelation
Bases: UNTPBaseModel
A typed cross-module relation between two entities.
Wire shape
{"relation": "eudpp:hasManufacturer",
"subject": "https://example.com/dpp/123",
"object": "https://example.com/actor/abc"}
The relation field is a compact eudpp: IRI from
:class:RelationType (or any other ontology-defined object
property). subject and object are URIs identifying the
related entities.
Cardinality:
relation: required (1).
subject: required (1).
object: required (1).
valid_from / valid_to: optional ISO 8601 datetimes —
enable temporally-scoped relations (e.g. an actor that played
a role only during a manufacturing batch).
Source code in src/dppvalidator/models/cirpass/v1_3/connector.py
| Python |
|---|
| class ConnectorRelation(UNTPBaseModel):
"""A typed cross-module relation between two entities.
Wire shape:
``{"relation": "eudpp:hasManufacturer",
"subject": "https://example.com/dpp/123",
"object": "https://example.com/actor/abc"}``
The ``relation`` field is a compact ``eudpp:`` IRI from
:class:`RelationType` (or any other ontology-defined object
property). ``subject`` and ``object`` are URIs identifying the
related entities.
Cardinality:
- ``relation``: required (1).
- ``subject``: required (1).
- ``object``: required (1).
- ``valid_from`` / ``valid_to``: optional ISO 8601 datetimes —
enable temporally-scoped relations (e.g. an actor that played
a role only during a manufacturing batch).
"""
_jsonld_type: ClassVar[list[str]] = ["ConnectorRelation"]
relation: str = Field(
...,
description=(
"Compact ``eudpp:`` IRI of the relation predicate. See "
":class:`RelationType` for the canonical set; downstream "
"taxonomies may use other IRIs."
),
)
subject: str = Field(
...,
min_length=1,
description="URI of the subject entity (the relation's source).",
)
object: str = Field(
...,
min_length=1,
description="URI of the object entity (the relation's target).",
)
valid_from: str | None = Field(
default=None,
alias="validFrom",
description="Optional ISO 8601 datetime: relation effective from.",
)
valid_to: str | None = Field(
default=None,
alias="validTo",
description="Optional ISO 8601 datetime: relation expires at.",
)
@field_validator("subject", "object")
@classmethod
def _looks_like_uri(cls, value: str) -> str:
# We don't enforce strict URI shape (downstream consumers may
# legitimately use compact ``eudpp:Foo`` form), but we reject
# bare-string values that obviously aren't identifiers.
if not value.strip():
msg = "ConnectorRelation subject/object must not be empty."
raise ValueError(msg)
return value
@property
def relation_type(self) -> RelationType | None:
"""Return the :class:`RelationType` member if ``relation`` matches one.
Returns ``None`` for relation IRIs outside the canonical set
(downstream extensions are valid — :class:`ConnectorRelation`
carries the IRI string, not an enforced enum).
"""
try:
return RelationType(self.relation)
except ValueError:
return None
|
relation_type
property
Return the :class:RelationType member if relation matches one.
Returns None for relation IRIs outside the canonical set
(downstream extensions are valid — :class:ConnectorRelation
carries the IRI string, not an enforced enum).
options:
show_source: false
show_bases: false
dppvalidator.models.cirpass.v1_3.RelationType
Bases: str, Enum
Known cross-module relation predicates (CIRPASS-2 CON v1.9.1).
Each member is a compact eudpp: IRI for a CON-module object
property. Downstream consumers that emit :class:ConnectorRelation
payloads with relation predicates outside this enum (e.g.
pilot-specific extensions) are still valid — :class:ConnectorRelation
carries the IRI as a string, not as an enforced enum.
Source code in src/dppvalidator/models/cirpass/v1_3/connector.py
| Python |
|---|
| class RelationType(str, Enum):
"""Known cross-module relation predicates (CIRPASS-2 CON v1.9.1).
Each member is a compact ``eudpp:`` IRI for a CON-module object
property. Downstream consumers that emit :class:`ConnectorRelation`
payloads with relation predicates *outside* this enum (e.g.
pilot-specific extensions) are still valid — :class:`ConnectorRelation`
carries the IRI as a string, not as an enforced enum.
"""
# CON-native relations (added to v1.9.1 connector module)
IS_CONNECTED_TO = "eudpp:isConnectedTo"
IN_CONTEXT_OF_ACTIVITY = "eudpp:inContextOfActivity"
IN_CONTEXT_OF_DPP = "eudpp:inContextOfDPP"
IN_CONTEXT_OF_PRODUCT = "eudpp:inContextOfProduct"
REPRESENTS_MANUFACTURER_FOR_PRODUCT = "eudpp:representsManufacturerForProduct"
# Migrated to CON in v1.9.1 (were in P_DPP v1.7.1)
HAS_ISSUER = "eudpp:hasIssuer"
HAS_MANUFACTURER = "eudpp:hasManufacturer"
HAS_ECONOMIC_OPERATOR = "eudpp:hasEconomicOperator"
HAS_BACK_UP_COPY_HOST = "eudpp:hasBackUpCopyHost"
CONTAINS_SUBSTANCE_OF_CONCERN = "eudpp:containsSubstanceOfConcern"
|
options:
show_source: false
show_bases: false
Multilingual labels
dppvalidator.models.cirpass.v1_3.LocalisedText
Bases: UNTPBaseModel
A text value tagged with a BCP-47 language identifier.
Wire shape: {"value": "Cotton t-shirt", "language": "en"}.
Used wherever the CIRPASS reference structure expects a
user-facing label that must surface in multiple official
languages — product name, description, classification labels,
etc. The set of fields requiring this wrapper is dictated by the
v1.9.1 SHACL shape audit (Phase 1 task 1.6); fields without the
multilingual requirement use plain :class:str.
Attributes:
| Name |
Type |
Description |
value |
str
|
The text content. Whitespace is trimmed by the base
model's str_strip_whitespace config.
|
language |
str
|
BCP-47 tag (en, de, zh-Hant,
en-US, etc.). Validated against the pragmatic-subset
grammar in :data:_BCP47_RE. Lower-case primary subtag,
initial-case script subtag, upper-case region subtag.
|
Source code in src/dppvalidator/models/cirpass/v1_3/i18n.py
| Python |
|---|
| class LocalisedText(UNTPBaseModel):
"""A text value tagged with a BCP-47 language identifier.
Wire shape: ``{"value": "Cotton t-shirt", "language": "en"}``.
Used wherever the CIRPASS reference structure expects a
user-facing label that must surface in multiple official
languages — product name, description, classification labels,
etc. The set of fields requiring this wrapper is dictated by the
v1.9.1 SHACL shape audit (Phase 1 task 1.6); fields without the
multilingual requirement use plain :class:`str`.
Attributes:
value: The text content. Whitespace is trimmed by the base
model's ``str_strip_whitespace`` config.
language: BCP-47 tag (``en``, ``de``, ``zh-Hant``,
``en-US``, etc.). Validated against the pragmatic-subset
grammar in :data:`_BCP47_RE`. Lower-case primary subtag,
initial-case script subtag, upper-case region subtag.
"""
_jsonld_type: ClassVar[list[str]] = ["LocalisedText"]
value: str = Field(
...,
description="The text value in the language identified by ``language``.",
min_length=1,
)
language: str = Field(
...,
description="BCP-47 language tag (e.g. ``en``, ``de``, ``zh-Hant``).",
)
@field_validator("language")
@classmethod
def _validate_bcp47(cls, value: str) -> str:
if not _BCP47_RE.match(value):
msg = (
f"language tag {value!r} is not a recognised BCP-47 form. "
f"Expected ``primary[-Script][-REGION][-variant]``, e.g. "
f"``en``, ``de``, ``zh-Hant``, ``en-GB``."
)
raise ValueError(msg)
return value
|
options:
show_source: false
show_bases: false
Temporal
dppvalidator.models.cirpass.v1_3.EffectivePeriod
Bases: UNTPBaseModel
The validity window during which a CIRPASS DPP applies.
Wire shape
{"start": "2026-01-01T00:00:00Z", "end": "2031-12-31T23:59:59Z"}
Both endpoints are ISO 8601 datetimes with UTC offset. start
is required; end is optional (a DPP with no end date is
effective indefinitely until superseded). When both are present,
start <= end is enforced via a :func:@model_validator.
Maps to UNTP 0.7's validFrom / validUntil envelope fields
via the Phase 5 compat shim.
Source code in src/dppvalidator/models/cirpass/v1_3/temporal.py
| Python |
|---|
| class EffectivePeriod(UNTPBaseModel):
"""The validity window during which a CIRPASS DPP applies.
Wire shape:
``{"start": "2026-01-01T00:00:00Z", "end": "2031-12-31T23:59:59Z"}``
Both endpoints are ISO 8601 datetimes with UTC offset. ``start``
is required; ``end`` is optional (a DPP with no end date is
effective indefinitely until superseded). When both are present,
``start <= end`` is enforced via a :func:`@model_validator`.
Maps to UNTP 0.7's ``validFrom`` / ``validUntil`` envelope fields
via the Phase 5 compat shim.
"""
_jsonld_type: ClassVar[list[str]] = ["EffectivePeriod"]
start: datetime = Field(
...,
description="UTC datetime from which the DPP is effective.",
)
end: datetime | None = Field(
default=None,
description=(
"UTC datetime after which the DPP is no longer effective. "
"Absent ⇒ effective indefinitely."
),
)
@model_validator(mode="after")
def _start_before_end(self) -> EffectivePeriod:
if self.end is not None and self.start > self.end:
msg = (
f"EffectivePeriod.start={self.start.isoformat()} is later "
f"than .end={self.end.isoformat()}; the period is empty. "
f"If the DPP is no longer effective, drop ``end`` and "
f"supersede with a new DPP instead."
)
raise ValueError(msg)
return self
|
options:
show_source: false
show_bases: false
dppvalidator.models.cirpass.v1_3.IssuedAt
Bases: UNTPBaseModel
Timestamp at which a CIRPASS DPP was issued.
Wire shape: {"timestamp": "2026-01-15T09:30:00Z"}.
Maps to UNTP 0.7's issuanceDate envelope field via the Phase 5
compat shim. Carries an explicit type-wrapper rather than a bare
datetime so that downstream LD payloads can attach
IssuedAt-scoped metadata (e.g. issuing-software, signature) on
the same node.
Source code in src/dppvalidator/models/cirpass/v1_3/temporal.py
| Python |
|---|
| class IssuedAt(UNTPBaseModel):
"""Timestamp at which a CIRPASS DPP was issued.
Wire shape: ``{"timestamp": "2026-01-15T09:30:00Z"}``.
Maps to UNTP 0.7's ``issuanceDate`` envelope field via the Phase 5
compat shim. Carries an explicit type-wrapper rather than a bare
datetime so that downstream LD payloads can attach
``IssuedAt``-scoped metadata (e.g. issuing-software, signature) on
the same node.
"""
_jsonld_type: ClassVar[list[str]] = ["IssuedAt"]
timestamp: datetime = Field(
...,
description="UTC datetime of issuance.",
)
@field_validator("timestamp")
@classmethod
def _require_tz_aware(cls, value: datetime) -> datetime:
if value.tzinfo is None:
msg = (
"IssuedAt.timestamp must be timezone-aware (typically UTC). "
"Naïve datetimes risk ambiguous interpretation across "
"issuer / consumer locales."
)
raise ValueError(msg)
return value
|
options:
show_source: false
show_bases: false