Skip to content

Commit eaf523a

Browse files
alpha912claude
andcommitted
feat: add ProductDNA schema — DPP-aligned product-level entity
Introduces ProductDNA as a higher-level entity for product reuse flows, aligned with EU ESPR Art. 9-10 and UNTP ProductPassport. Updates Offer, Match, and Transfer schemas to accept either material_id or product_id via JSON Schema anyOf. Adds JSON-LD context terms, OpenAPI endpoints, specification section 4.5, and two new validated examples. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1c84fae commit eaf523a

12 files changed

+921
-9
lines changed

SPECIFICATION.md

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
3. [Protocol Overview](#3-protocol-overview)
1717
3.5. [Minimal Interop Flow v0.1.1](#35-minimal-interop-flow-v011)
1818
4. [MaterialDNA Specification](#4-materialdna-specification)
19+
4.5. [ProductDNA Specification](#45-productdna-specification)
1920
5. [LoopCoin Specification](#5-loopcoin-specification)
2021
6. [LoopSignal Specification](#6-loopsignal-specification)
2122
7. [LoopCost Calculation](#7-loopcost-calculation)
@@ -95,6 +96,8 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S
9596

9697
**MaterialDNA**: Globally unique identifier for any material item or batch
9798

99+
**ProductDNA**: Globally unique identifier for a product or product batch, referencing constituent MaterialDNA entries
100+
98101
**LoopCoin (LC)**: Local digital currency with expiry properties
99102

100103
**LoopSignal**: Community-expressed preference percentage for material categories
@@ -174,6 +177,8 @@ Payloads using `schema_version: "0.1.1"` remain valid against v0.2.0 schemas (ba
174177

175178
- **MaterialDNA**
176179
Schema: `https://local-loop-io.github.io/projects/loop-protocol/schemas/v0.2.0/material-dna.schema.json`
180+
- **ProductDNA**
181+
Schema: `https://local-loop-io.github.io/projects/loop-protocol/schemas/v0.2.0/product-dna.schema.json`
177182
- **Offer**
178183
Schema: `https://local-loop-io.github.io/projects/loop-protocol/schemas/v0.2.0/offer.schema.json`
179184
- **Match**
@@ -317,6 +322,141 @@ electronics/
317322

318323
---
319324

325+
## 4.5 ProductDNA Specification
326+
327+
### 4.5.1 Overview
328+
329+
ProductDNA represents the **product layer** in LOOP's two-tier hierarchy: materials compose products. While MaterialDNA tracks raw or processed materials (e.g., plastic-hdpe, metal-copper), ProductDNA tracks finished or semi-finished products (e.g., office desks, laptops) that contain those materials.
330+
331+
This separation aligns with:
332+
- **EU ESPR (Art. 9-10)**: Digital Product Passports are defined at the product level
333+
- **UNTP ProductPassport**: Materials are nested inside product passports
334+
- **GS1/EPCIS**: Trade items (products) are the primary tracking unit
335+
336+
### 4.5.2 Hierarchy
337+
338+
```
339+
MaterialDNA (composition layer) ProductDNA (product layer, DPP-facing)
340+
├── category: plastic-hdpe ├── product_category: electronics-laptop
341+
├── quantity: 2kg ├── material_ids: [DE-MUC-2025-PLASTIC-..., ...]
342+
├── origin_city: Munich ├── condition: good
343+
└── passport (DPP fields) ├── passport (DPP fields, ESPR-aligned)
344+
└── lifecycle_stage: in-use
345+
```
346+
347+
A ProductDNA entry MAY reference zero or more MaterialDNA entries via `material_ids`. This composition link enables traceability from product to constituent materials.
348+
349+
### 4.5.3 Identifier Format
350+
351+
ProductDNA identifiers MUST follow this pattern:
352+
353+
```bash
354+
PRD-{COUNTRY}-{CITY}-{YEAR}-{CATEGORY}-{UNIQUE}
355+
```
356+
357+
Where:
358+
- `PRD`: Fixed prefix identifying a product
359+
- `COUNTRY`: ISO 3166-1 alpha-2 code
360+
- `CITY`: Three-letter city code
361+
- `YEAR`: Four-digit year of registration
362+
- `CATEGORY`: Product category keyword
363+
- `UNIQUE`: Alphanumeric unique identifier (min 6 characters)
364+
365+
Example:
366+
```bash
367+
PRD-DE-MUC-2025-DESK-F4A7B2
368+
```
369+
370+
### 4.5.4 ProductDNA Object
371+
372+
```json
373+
{
374+
"@context": "https://local-loop-io.github.io/projects/loop-protocol/contexts/loop-v0.2.0.jsonld",
375+
"@type": "ProductDNA",
376+
"schema_version": "0.2.0",
377+
"id": "PRD-DE-MUC-2025-DESK-F4A7B2",
378+
"product_category": "furniture-office",
379+
"name": "Standing Desk — Ergotron WorkFit",
380+
"condition": "good",
381+
"quantity": { "value": 12, "unit": "piece" },
382+
"origin_city": "Munich",
383+
"current_city": "Munich",
384+
"available_from": "2026-03-15T08:00:00Z",
385+
"manufacturer": "Ergotron",
386+
"model": "WorkFit-S",
387+
"manufacture_year": 2021,
388+
"functional_status": "fully-functional",
389+
"lifecycle_stage": "end-of-first-use",
390+
"material_ids": [
391+
"DE-MUC-2025-METAL-4EB84C",
392+
"DE-MUC-2025-PLASTIC-96FE78"
393+
]
394+
}
395+
```
396+
397+
### 4.5.5 Required Fields
398+
399+
- `schema_version`: Schema version (0.2.0)
400+
- `id`: Unique ProductDNA identifier (PRD- prefix)
401+
- `product_category`: Standardized product category
402+
- `name`: Product name (2-200 characters)
403+
- `condition`: Physical condition (new, like-new, good, fair, poor, for-parts)
404+
- `quantity`: Amount and unit
405+
- `origin_city`: Registering city
406+
- `current_city`: Current custodian city
407+
- `available_from`: When product becomes available
408+
409+
### 4.5.6 Product Categories
410+
411+
```
412+
furniture/
413+
furniture-office # Office furniture
414+
furniture-residential # Residential furniture
415+
furniture-industrial # Industrial furniture
416+
417+
building/
418+
building-structural # Structural building components
419+
building-fixture # Fixtures and fittings
420+
building-hvac # HVAC equipment
421+
building-electrical # Electrical installations
422+
423+
electronics/
424+
electronics-computing # Computers and peripherals
425+
electronics-mobile # Mobile devices
426+
electronics-appliance # Household appliances
427+
electronics-components # Electronic components
428+
429+
textiles/
430+
textile-garment # Garments and clothing
431+
textile-industrial # Industrial textiles
432+
433+
packaging/
434+
packaging-reusable # Reusable packaging
435+
436+
vehicles/
437+
vehicle-parts # Vehicle parts and components
438+
439+
equipment/
440+
equipment-industrial # Industrial equipment
441+
equipment-medical # Medical equipment
442+
```
443+
444+
### 4.5.7 Offer/Match/Transfer with Products
445+
446+
The Offer, Match, and Transfer schemas accept either `material_id` or `product_id`. At least one MUST be present. This allows the same lifecycle flow (Offer → Match → Transfer) to work for both materials and products.
447+
448+
```json
449+
{
450+
"@type": "Offer",
451+
"product_id": "PRD-DE-MUC-2025-DESK-F4A7B2",
452+
"from_city": "Munich",
453+
"to_city": "Berlin",
454+
...
455+
}
456+
```
457+
458+
---
459+
320460
## 5. LoopCoin Specification
321461

322462
### 5.1 Currency Properties
@@ -1058,6 +1198,8 @@ POST munich.loop/api/v1/federate/offer
10581198
- Conformity claims model (UNTP-aligned)
10591199
- EPCIS event references and W3C VC pointers in traceability blocks
10601200
- Schema versioning policy (RFC-0003)
1201+
- **ProductDNA schema** — product-level DPP entity referencing MaterialDNA composition
1202+
- Offer/Match/Transfer schemas accept `product_id` as alternative to `material_id`
10611203

10621204
### 13.2 Future Features
10631205

@@ -1141,6 +1283,10 @@ Key properties of the v0.2.0 context:
11411283
- Clarified dual license (MIT for code, CC BY-SA 4.0 for prose)
11421284
- Established schema versioning policy (RFC-0003)
11431285
- Backend API paths aligned to `/api/v1/`
1286+
- Added ProductDNA schema (product-level DPP entity, ESPR Art. 9-10 aligned)
1287+
- Offer/Match/Transfer schemas now accept `product_id` as alternative to `material_id` (anyOf)
1288+
- Added product category enum (17 categories across furniture, building, electronics, textile, packaging, vehicle, equipment)
1289+
- Added ProductDNA term mappings to JSON-LD context
11441290

11451291
### Version 0.1.1 (2025-12-20)
11461292

contexts/loop-v0.2.0.jsonld

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
},
1919

2020
"MaterialDNA": "loop:MaterialDNA",
21+
"ProductDNA": "loop:ProductDNA",
2122
"Offer": "loop:Offer",
2223
"Match": "loop:Match",
2324
"Transfer": "loop:Transfer",
@@ -202,6 +203,27 @@
202203
"temperature_c": "loop:temperature_c",
203204
"humidity_percent": "loop:humidity_percent",
204205

206+
"product_id": "loop:product_id",
207+
"product_category": "loop:product_category",
208+
"condition": "loop:condition",
209+
"manufacturer": "loop:manufacturer",
210+
"model": "loop:model",
211+
"manufacture_year": "loop:manufacture_year",
212+
"functional_status": "loop:functional_status",
213+
"lifecycle_stage": "loop:lifecycle_stage",
214+
"material_ids": "loop:material_ids",
215+
"dimensions": "loop:dimensions",
216+
"length_cm": "loop:length_cm",
217+
"width_cm": "loop:width_cm",
218+
"height_cm": "loop:height_cm",
219+
"weight_kg": "loop:weight_kg",
220+
"reuse_potential": "loop:reuse_potential",
221+
"issued_at": {
222+
"@id": "loop:issued_at",
223+
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
224+
},
225+
"issuer_node": "loop:issuer_node",
226+
205227
"material_id": "loop:material_id",
206228
"offer_id": "loop:offer_id",
207229
"match_id": "loop:match_id",
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"@context": "https://local-loop-io.github.io/projects/loop-protocol/contexts/loop-v0.2.0.jsonld",
3+
"@type": "ProductDNA",
4+
"schema_version": "0.2.0",
5+
"id": "PRD-DE-MUC-2025-DESK-F4A7B2",
6+
"product_category": "furniture-office",
7+
"name": "Standing Desk — Ergotron WorkFit",
8+
"condition": "good",
9+
"quantity": { "value": 12, "unit": "piece" },
10+
"origin_city": "Munich",
11+
"current_city": "Munich",
12+
"available_from": "2026-03-15T08:00:00Z",
13+
"manufacturer": "Ergotron",
14+
"model": "WorkFit-S",
15+
"manufacture_year": 2021,
16+
"functional_status": "fully-functional",
17+
"lifecycle_stage": "end-of-first-use",
18+
"material_ids": [
19+
"DE-MUC-2025-METAL-4EB84C",
20+
"DE-MUC-2025-PLASTIC-96FE78"
21+
],
22+
"dimensions": {
23+
"length_cm": 120,
24+
"width_cm": 60,
25+
"height_cm": 130,
26+
"weight_kg": 18.5
27+
},
28+
"reuse_potential": "Fully functional standing desks from office clearance. Minor cosmetic wear.",
29+
"passport": {
30+
"passport_id": "PP-PRD-2026-F4A7B2",
31+
"issued_at": "2026-03-10T10:00:00Z",
32+
"issuer_node": "munich.loop",
33+
"repair_score": 8,
34+
"durability_score": 9
35+
}
36+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
[
2+
{
3+
"@context": "https://local-loop-io.github.io/projects/loop-protocol/contexts/loop-v0.2.0.jsonld",
4+
"@type": "Offer",
5+
"schema_version": "0.2.0",
6+
"id": "OFR-PRD-8C4F2A1B",
7+
"product_id": "PRD-DE-MUC-2025-DESK-F4A7B2",
8+
"from_city": "Munich",
9+
"to_city": "Berlin",
10+
"quantity": { "value": 12, "unit": "piece" },
11+
"status": "open",
12+
"available_until": "2026-03-20T18:00:00Z",
13+
"terms": "Pickup from Munich office building. Palletized and ready for transport."
14+
},
15+
{
16+
"@context": "https://local-loop-io.github.io/projects/loop-protocol/contexts/loop-v0.2.0.jsonld",
17+
"@type": "Match",
18+
"schema_version": "0.2.0",
19+
"id": "MCH-PRD-3D7E9F45",
20+
"product_id": "PRD-DE-MUC-2025-DESK-F4A7B2",
21+
"offer_id": "OFR-PRD-8C4F2A1B",
22+
"from_city": "Munich",
23+
"to_city": "Berlin",
24+
"status": "accepted",
25+
"matched_at": "2026-03-16T14:30:00Z"
26+
},
27+
{
28+
"@context": "https://local-loop-io.github.io/projects/loop-protocol/contexts/loop-v0.2.0.jsonld",
29+
"@type": "Transfer",
30+
"schema_version": "0.2.0",
31+
"id": "TRF-PRD-1A5B8C2D",
32+
"product_id": "PRD-DE-MUC-2025-DESK-F4A7B2",
33+
"match_id": "MCH-PRD-3D7E9F45",
34+
"status": "completed",
35+
"handoff_at": "2026-03-18T09:00:00Z",
36+
"received_at": "2026-03-18T17:30:00Z",
37+
"route": {
38+
"from_city": "Munich",
39+
"to_city": "Berlin",
40+
"mode": "road"
41+
}
42+
}
43+
]

examples/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ This directory contains JSON examples that validate against the schemas in `sche
1515
- `11-handshake-response.json`: Node handshake response payload.
1616
- `12-material-dna-dpp-extensions.json`: MaterialDNA with comprehensive DPP passport fields (v0.2.0).
1717
- `13-conformity-claims.json`: MaterialDNA with multiple conformity claims (v0.2.0).
18+
- `14-product-reuse-registration.json`: ProductDNA registration for office furniture reuse (v0.2.0).
19+
- `15-product-offer-flow.json`: Complete Offer→Match→Transfer flow using product_id (v0.2.0).
1820

1921
## Validation
2022
Run `npm test` from the repository root to validate all examples.

openapi.json

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
],
1919
"tags": [
2020
{ "name": "Material" },
21+
{ "name": "Product" },
2122
{ "name": "Node" },
2223
{ "name": "Signals" },
2324
{ "name": "Transactions" },
@@ -34,6 +35,9 @@
3435
"MaterialDNA": {
3536
"$ref": "https://local-loop-io.github.io/projects/loop-protocol/schemas/v0.2.0/material-dna.schema.json"
3637
},
38+
"ProductDNA": {
39+
"$ref": "https://local-loop-io.github.io/projects/loop-protocol/schemas/v0.2.0/product-dna.schema.json"
40+
},
3741
"Offer": {
3842
"$ref": "https://local-loop-io.github.io/projects/loop-protocol/schemas/v0.2.0/offer.schema.json"
3943
},
@@ -196,6 +200,51 @@
196200
}
197201
}
198202
},
203+
"/product": {
204+
"post": {
205+
"tags": ["Product"],
206+
"summary": "Register ProductDNA",
207+
"requestBody": {
208+
"required": true,
209+
"content": {
210+
"application/ld+json": {
211+
"schema": { "$ref": "#/components/schemas/ProductDNA" }
212+
}
213+
}
214+
},
215+
"responses": {
216+
"201": {
217+
"description": "Created",
218+
"content": {
219+
"application/ld+json": {
220+
"schema": { "$ref": "#/components/schemas/ProductDNA" }
221+
}
222+
}
223+
},
224+
"400": { "description": "Invalid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
225+
}
226+
}
227+
},
228+
"/product/{id}": {
229+
"get": {
230+
"tags": ["Product"],
231+
"summary": "Get ProductDNA by ID",
232+
"parameters": [
233+
{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
234+
],
235+
"responses": {
236+
"200": {
237+
"description": "OK",
238+
"content": {
239+
"application/ld+json": {
240+
"schema": { "$ref": "#/components/schemas/ProductDNA" }
241+
}
242+
}
243+
},
244+
"404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
245+
}
246+
}
247+
},
199248
"/node/info": {
200249
"get": {
201250
"tags": ["Node"],

schemas/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ JSON Schema definitions for LOOP payloads.
44

55
## Files
66
- `material-dna.schema.json` — Material identity and metadata (v0.2.0)
7-
- `offer.schema.json` — Offer payload definition (v0.2.0)
8-
- `match.schema.json` — Match payload definition (v0.2.0)
9-
- `transfer.schema.json` — Transfer payload definition (v0.2.0)
7+
- `product-dna.schema.json` — Product identity and metadata, DPP-aligned (v0.2.0)
8+
- `offer.schema.json` — Offer payload definition (v0.2.0, supports material_id or product_id)
9+
- `match.schema.json` — Match payload definition (v0.2.0, supports material_id or product_id)
10+
- `transfer.schema.json` — Transfer payload definition (v0.2.0, supports material_id or product_id)
1011
- `material-status.schema.json` — Material status updates (v0.2.0)
1112
- `handshake.schema.json` — Federation handshake protocol (v0.2.0)
1213
- `loopcoin.schema.json`

0 commit comments

Comments
 (0)