Migrating from UNTP DPP 0.7.0 to CIRPASS reference structure v1.3.0¶
UNTP DPP and the CIRPASS-2 reference-structure message are two different families of DPP payload, not two versions of the same format. dppvalidator ships two-way compat shims that project between them:
dppvalidator.compat.to_cirpass_1_3— forward (UNTP → CIRPASS).dppvalidator.compat.to_untp_0_7— reverse (CIRPASS → UNTP).
The shims are lossy outside a documented subset. This guide:
- Shows how to run them from the CLI and Python.
- Walks through a before/after JSON example.
- Lists the five
MAP00Xwarning codes and what they mean. - Captures the round-trip identity (lossless subset) so you know what survives a forward+reverse cycle.
The conceptual overview lives at
cirpass-2-alignment.md; the
complete field-by-field mapping table is at
untp-cirpass-mapping.md.
When you need this¶
- You have UNTP 0.7.0 DPPs in production and a downstream consumer asks for CIRPASS-shaped output.
- You're publishing CIRPASS reference-structure messages and want to convert them to UNTP 0.7.0 for a UN/CEFACT-side workflow.
- You want to add a CIRPASS-side validation pass to an existing UNTP pipeline without re-authoring fixtures.
Quick start¶
CLI — UNTP → CIRPASS¶
# Project a UNTP 0.7.0 payload onto CIRPASS reference structure v1.3.0.
# Default refuses to write when warnings fire.
dppvalidator migrate untp-passport.json --to cirpass-1.3 -o cirpass.json
# Accept lossy/synthesis warnings and proceed. The sidecar
# cirpass.json.warnings.json captures every MAP00X warning.
dppvalidator migrate untp-passport.json \
--to cirpass-1.3 \
-o cirpass.json \
--accept-warnings
# Override the synthesised default language for multilingual labels:
dppvalidator migrate untp-passport.json --to cirpass-1.3 \
-o cirpass.json --default-language de --accept-warnings
CLI — CIRPASS → UNTP¶
# Project a CIRPASS payload onto UNTP 0.7.0 envelope.
dppvalidator migrate cirpass.json --to untp-0.7 -o untp.json --accept-warnings
Note: The reverse direction (
--to untp-0.7) is only available for CIRPASS inputs in Phase 5+. For UNTP 0.6 → 0.7 intra-family upgrades, the same--to untp-0.7flag still routes through the legacy v0.6 → v0.7 shim — see migration-0-6-to-0-7.md.
CLI — JSON-LD export shortcut¶
If you don't need a sidecar JSON file but just want a CIRPASS- shaped JSON-LD document, the export command does both the projection and the JSON-LD wrapping in one step:
The exporter forwards mapping warnings to stderr so stdout stays
pipe-clean for ... | jq consumers.
Python¶
import json
from dppvalidator.compat import to_cirpass_1_3, to_untp_0_7, MAP_CODE_LOSSY
untp_dict = json.load(open("untp-passport.json"))
# Forward
cirpass_dict, warnings = to_cirpass_1_3(
untp_dict,
default_language="en",
)
# Inspect warnings
for w in warnings:
print(f"[{w.code}] {w.severity.value} {w.path}: {w.message}")
# Validate the projected output
from dppvalidator.models.cirpass.v1_3 import ReferencePassport
ReferencePassport.model_validate(cirpass_dict)
# Reverse
back, rev_warnings = to_untp_0_7(cirpass_dict, default_language="en")
Before / after — minimal example¶
UNTP 0.7.0 input¶
{
"@context": [
"https://www.w3.org/ns/credentials/v2",
"https://vocabulary.uncefact.org/untp/0.7.0/context/"
],
"type": ["DigitalProductPassport", "VerifiableCredential"],
"id": "https://example.com/dpp/CR-001",
"name": "Cotton T-shirt DPP",
"issuer": {
"id": "https://example.com/lei/529900T8BM49AURSDO55",
"name": "Example Apparel Ltd.",
"type": ["CredentialIssuer"]
},
"validFrom": "2026-05-08T00:00:00+00:00",
"validUntil": "2031-05-08T00:00:00+00:00",
"credentialSubject": {
"type": ["Product"],
"id": "01234567890128",
"name": "Cotton T-shirt",
"idScheme": {"id": "https://gs1.org/voc/", "name": "GS1 GTIN"},
"idGranularity": "model",
"productCategory": [
{
"schemeId": "https://www.wcoomd.org/en/topics/nomenclature/instrument-and-tools/hs-nomenclature-2022-edition.aspx",
"schemeName": "WCO HS",
"code": "61091000",
"name": "T-shirts"
}
],
"producedAtFacility": {"id": "https://example.com/f", "name": "F", "type": ["Facility"]},
"countryOfProduction": {"countryCode": "DE", "countryName": "Germany"},
"materialProvenance": [
{
"name": "Cotton",
"originCountry": {"countryCode": "DE"},
"materialType": {
"schemeId": "https://w3id.org/eudpp#MaterialType",
"schemeName": "ISO 2076",
"code": "CO",
"name": "Cotton"
},
"massFraction": 1.0
}
]
}
}
CIRPASS reference structure v1.3.0 output (after to_cirpass_1_3)¶
{
"dppIdentifier": {
"value": "https://example.com/dpp/CR-001",
"scheme": "https://example.com/dpp-register/",
"schemeName": "DPP register"
},
"product": {
"productIdentifier": {
"value": "01234567890128",
"scheme": "https://gs1.org/voc/",
"schemeName": "GS1 GTIN"
},
"productName": [
{"value": "Cotton T-shirt", "language": "en"}
],
"commodityCode": [
{
"code": "61091000",
"scheme": "https://www.wcoomd.org/en/topics/nomenclature/instrument-and-tools/hs-nomenclature-2022-edition.aspx",
"name": [{"value": "T-shirts", "language": "en"}]
}
]
},
"issuedAt": {"timestamp": "2026-05-08T00:00:00+00:00"},
"effectivePeriod": {
"start": "2026-05-08T00:00:00+00:00",
"end": "2031-05-08T00:00:00+00:00"
},
"composition": {
"materials": [
{
"materialName": [{"value": "Cotton", "language": "en"}],
"materialType": "CO",
"originCountry": "DE",
"massFraction": "1.0"
}
]
},
"relatedActors": [
{
"actor": {
"actorIdentifier": {
"value": "https://example.com/lei/529900T8BM49AURSDO55",
"scheme": "https://www.gleif.org/lei/",
"schemeName": "GLEIF LEI"
},
"actorName": [{"value": "Example Apparel Ltd.", "language": "en"}]
},
"role": "eudpp:ManufacturerRole"
}
]
}
Warnings emitted on the projection¶
The forward shim emits five MAP002 warnings on this payload — one
for each synthesised value (DPP-register scheme URI; the language
tag on every LocalisedText entry the shim wraps; a manufacturer
actor lifted from issuer).
[MAP002] (warning) $.dppIdentifier.scheme: DPP-register scheme synthesised — UNTP carries the credential id but no register URI; using a placeholder.
[MAP002] (warning) $.product.productName[0].language: UNTP credentialSubject.name is a scalar string; synthesised a single CIRPASS LocalisedText entry with language='en'.
[MAP002] (warning) $.product.commodityCode[0].name[0].language: UNTP Classification.name is a scalar string; projected onto a single LocalisedText with language='en'.
[MAP002] (warning) $.composition.materials[0].materialName[0].language: UNTP Material.name is a scalar string; projected onto a single LocalisedText with language='en'.
The shim does not synthesise content (e.g. the recycled-mass fraction is absent on the input, so it's absent on the output).
Warning codes¶
The five MAP00X codes the shim may emit:
| Code | Meaning | Default severity |
|---|---|---|
MAP001 |
Lossy — target shape drops information. Examples: dropping a non-default-language LocalisedText entry; dropping CIRPASS-only fields (substancesOfConcern, connectorRelations, lca) on the reverse leg. |
warning |
MAP002 |
Synthesised — required field invented from a donor (e.g. wrapping a UNTP scalar name in a single-entry LocalisedText). | warning |
MAP003 |
Unmapped — passthrough (no rule applied). Fires when the input carries a scheme URI / role IRI / etc. that's not in the bundled lookup table. | info |
MAP004 |
Required-field-missing — the source cannot supply a field the target requires. The output will not validate against the target Pydantic model until you fill the field in. | error |
MAP005 |
Temporal collapse — less-expressive target temporal shape (e.g. validUntil absent ⇒ effectivePeriod.end left empty). |
warning |
Lossless subset¶
The following fields round-trip identity-preserving — pass through both shims unchanged when the input only contains them:
dppIdentifier.value↔idproduct.productIdentifier.{value, scheme, schemeName}↔credentialSubject.{id, idScheme.{id, name}}for schemes registered in the bundled lookup tableissuedAt.timestamp↔validFromeffectivePeriod.{start, end}↔(validFrom, validUntil)when both endpoints populate- Single-language
productName[0]↔namewhen language matches the caller'sdefault_language - A single related-actor with
ManufacturerRole↔ envelopeissuer+ a singlerelatedPartyentry with rolemanufacturer
The Hypothesis property test at
tests/property/test_round_trip_invariants.py
exercises this invariant against 200 generated examples per direction.
Limitations¶
The shim will not:
- Synthesise content the input doesn't carry. Missing
producedAtFacilitybecomes a placeholder; missingmaterialType.codebecomes"unspecified". - Translate UNTP
performanceClaimentries into CIRPASS LCA results. The Phase 7 textile-v2 pilot extends this for the microplastic / durability / recycled-content topics, but the base shim drops every claim with a singleMAP001. - Translate CIRPASS
substancesOfConcerninto a UNTP base representation — there isn't one. Use thedppvalidator-textilesplugin (when published) for textile-side substance tracking.
Round-trip identity check¶
The forward shim is idempotent over the lossless subset, but a forward+reverse cycle on a non-subset payload will not recover the original. If you want to verify round-trip behaviour for your specific input shape, run:
import json
from dppvalidator.compat import to_cirpass_1_3, to_untp_0_7
untp = json.load(open("untp.json"))
cirpass, _ = to_cirpass_1_3(untp)
recovered, _ = to_untp_0_7(cirpass)
# Compare the lossless-subset fields
assert recovered["id"] == untp["id"]
assert recovered["validFrom"] == untp["validFrom"]
assert recovered.get("validUntil") == untp.get("validUntil")
assert recovered["credentialSubject"]["id"] == untp["credentialSubject"]["id"]
See also¶
untp-cirpass-mapping.md— field-by-field mapping table.cirpass-2-alignment.md— big-picture orientation.migration-0-6-to-0-7.md— UNTP intra-family upgrade (0.6 → 0.7).- CLI exit codes.