@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix eubat: <https://ref.openepcis.io/extensions/eu/battery/> .
@prefix oec: <https://ref.openepcis.io/extensions/common/core/> .
@prefix gs1: <https://ref.gs1.org/voc/> .
@prefix schema: <https://schema.org/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix dcterms: <http://purl.org/dc/terms/> .
# Reusable value shapes from the core shapes graph (dpp-sh:TranslatableText).
# Every validation type bundles dpp-core-shapes.ttl alongside this file.
@prefix dpp-sh: <https://ref.openepcis.io/extensions/common/core/shapes/> .

<https://ref.openepcis.io/extensions/eu/battery/shapes/>
    a sh:ShapesGraph ;
    dcterms:title "OpenEPCIS Battery SHACL Shapes"@en ;
    dcterms:description "SHACL validation shapes for Battery Digital Product Passport vocabulary, aligned with BatteryPass-Ready Data Attribute Longlist v1.3 (March 2026)."@en ;
    dcterms:created "2025-02-02"^^xsd:date ;
    dcterms:modified "2026-06-07"^^xsd:date .

# =============================================================================
# OpenEPCIS Battery Vocabulary SHACL Shapes v0.9.6
# Basic validation shapes for Battery Digital Product Passport data
# =============================================================================

# -----------------------------------------------------------------------------
# Battery Shape
# -----------------------------------------------------------------------------
eubat:BatteryShape a sh:NodeShape ;
    sh:targetClass eubat:Battery ;
    rdfs:label "Battery Shape" ;
    rdfs:comment "Validation shape for Battery instances as defined by EU Battery Regulation 2023/1542." ;
    sh:property [
        sh:path schema:category ;
        sh:name "Battery Category" ;
        sh:description "The regulatory category of the battery (LMT, EV, Industrial, Stationary)." ;
        sh:maxCount 1 ;
        sh:class eubat:BatteryCategory
    ] ;
    sh:property [
        sh:path schema:status ;
        sh:name "Battery Status" ;
        sh:description "Current lifecycle status of the battery." ;
        sh:maxCount 1 ;
        sh:class eubat:BatteryStatus
    ] ;
    sh:property [
        sh:path eubat:hasBatteryChemistry ;
        sh:name "Battery Chemistry" ;
        sh:class eubat:BatteryChemistry
    ] ;
    sh:property [
        sh:path eubat:hasTechnicalSpecifications ;
        sh:name "Technical Specifications" ;
        sh:class eubat:TechnicalSpecification
    ] ;
    sh:property [
        sh:path eubat:hasMaterialComposition ;
        sh:name "Material Composition" ;
        sh:class eubat:BatteryMaterial
    ] ;
    sh:property [
        sh:path eubat:hasRecycledContent ;
        sh:name "Recycled Content" ;
        sh:class eubat:RecycledContent
    ] ;
    sh:property [
        sh:path eubat:hasEndOfLifeInfo ;
        sh:name "End of Life Information" ;
        sh:class eubat:EndOfLifeInfo
    ] ;
    sh:property [
        sh:path eubat:hasHazardousSubstances ;
        sh:name "Hazardous Substances" ;
        sh:class eubat:HazardousSubstance
    ] ;
    sh:property [
        sh:path eubat:hasOperatorInformation ;
        sh:name "Operator Information" ;
        sh:class oec:OperatorInformation
    ] ;
    sh:property [
        sh:path eubat:hasSupplyChainDueDiligence ;
        sh:name "Supply Chain Due Diligence" ;
        sh:class eubat:SupplyChainDueDiligence
    ] ;
    # BatteryPass-Ready v1.3 mandatory identifier attributes (#7).
    # A model identifier is required at EVERY granularity: a batch or item
    # passport still says which model it is an instance of.
    sh:property [
        sh:path eubat:batteryModelIdentifier ;
        sh:name "Battery Model Identifier" ;
        sh:description "Mandatory per BatteryPass-Ready v1.3 (#7). Manufacturer-assigned model ID; referenced by the carbon footprint declaration." ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1
    ] ;
    # gs1:hasSerialNumber, eubat:facilityIdentifier, eubat:operatorIdentifier,
    # eubat:manufacturerIdentifier and gs1:manufacturer are GRANULARITY-DEPENDENT
    # and live in the per-level shapes at the end of this file — see the note there.
    # Their value constraints (datatype, cardinality) that hold at every level stay here.
    sh:property [
        sh:path gs1:hasSerialNumber ;
        sh:name "Battery Serial Number" ;
        sh:description "Mandatory per BatteryPass-Ready v1.3 (#8) at item granularity. Required on the Declaration of Conformity." ;
        sh:datatype xsd:string ;
        sh:maxCount 1
    ] ;
    sh:property [
        sh:path eubat:facilityIdentifier ;
        sh:name "Facility Identifier" ;
        sh:description "Mandatory per BatteryPass-Ready v1.3 (#11). Recommend GLN of the gs1:Place referenced by eubat:hasManufacturingPlace." ;
        sh:datatype xsd:string
    ] ;
    sh:property [
        sh:path eubat:operatorIdentifier ;
        sh:name "Economic Operator Identifier" ;
        sh:description "Mandatory per BatteryPass-Ready v1.3 (#9). Access-restricted (AuthorizedOnly)." ;
        sh:datatype xsd:string
    ] ;
    sh:property [
        sh:path eubat:manufacturerIdentifier ;
        sh:name "Manufacturer Identifier" ;
        sh:description "Mandatory per BatteryPass-Ready v1.3 (#10). Access-restricted (AuthorizedOnly)." ;
        sh:datatype xsd:string
    ] ;
    # Party master data is RESOLVER-SERVED: an event or passport references the
    # party by its GLN Digital Link and a consumer dereferences it, so the
    # organization node is deliberately NOT inlined (see
    # extensions/common/core/docs/EPCIS_MASTERDATA_AND_EXTENSIONS.md, rule 1).
    # A bare sh:class therefore rejects the very form the architecture prescribes.
    # Accept either: an inlined operator/organization node, or an IRI reference.
    sh:property [
        sh:path gs1:manufacturer ;
        sh:name "Manufacturer Information" ;
        sh:description "Mandatory per BatteryPass-Ready v1.3 (#13)." ;
        sh:or (
            [ sh:class oec:OperatorInformation ]
            [ sh:class gs1:Organization ]
            [ sh:nodeKind sh:IRI ]
        )
    ] ;
    # BatteryPass-Ready v1.3 cross-cutting DPP information (#1-#4)
    sh:property [
        sh:path schema:schemaVersion ;
        sh:name "DPP Schema Version" ;
        sh:description "Mandatory per BatteryPass-Ready v1.3 (#1). Identifies the schema version this DPP instance follows (e.g. \"1.3\")." ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1
    ] ;
    sh:property [
        sh:path oec:passportStatus ;
        sh:name "DPP Status" ;
        sh:description "Mandatory per BatteryPass-Ready v1.3 (#2). EN 18223 dppStatus." ;
        sh:datatype xsd:string ;
        sh:in ( "active" "inactive" "archived" "invalid" "draft" "withdrawn" "suspended" ) ;
        sh:minCount 1 ;
        sh:maxCount 1
    ] ;
    sh:property [
        sh:path oec:hasReportingGranularity ;
        sh:name "DPP Reporting Granularity" ;
        sh:description "Mandatory per BatteryPass-Ready v1.3 (#3)." ;
        sh:class oec:DPPGranularity ;
        sh:minCount 1 ;
        sh:maxCount 1
    ] ;
    sh:property [
        sh:path oec:lastUpdated ;
        sh:name "Date-time of latest update" ;
        sh:description "Mandatory per BatteryPass-Ready v1.3 (#4). Updated whenever any passport attribute changes." ;
        sh:datatype xsd:dateTime ;
        sh:minCount 1 ;
        sh:maxCount 1
    ] .

# -----------------------------------------------------------------------------
# Carbon Footprint Declaration Shape
# Carbon footprint *label* (#23) is MANDATORY in v1.3 (was voluntary in v1.2).
# -----------------------------------------------------------------------------
eubat:CarbonFootprintDeclarationShape a sh:NodeShape ;
    sh:targetClass eubat:CarbonFootprintDeclaration ;
    rdfs:label "Carbon Footprint Declaration Shape" ;
    sh:property [
        sh:path eubat:hasCarbonFootprintTotal ;
        sh:name "Battery Carbon Footprint per Functional Unit" ;
        sh:minCount 1
    ] ;
    sh:property [
        sh:path eubat:hasCarbonFootprintPerformanceClass ;
        sh:name "Carbon Footprint Performance Class" ;
        sh:class eubat:CarbonFootprintClass ;
        sh:minCount 1
    ] ;
    sh:property [
        sh:path eubat:carbonFootprintStudyUrl ;
        sh:name "Web link to public carbon footprint study" ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1
    ] .

# -----------------------------------------------------------------------------
# Battery Chemistry Shape
# -----------------------------------------------------------------------------
eubat:BatteryChemistryShape a sh:NodeShape ;
    sh:targetClass eubat:BatteryChemistry ;
    rdfs:label "Battery Chemistry Shape" ;
    sh:property [
        sh:path schema:name ;
        sh:name "Short Name" ;
        sh:description "Abbreviated chemistry name (e.g., LFP, NMC, NCA)." ;
        sh:node dpp-sh:TranslatableText
    ] ;
    sh:property [
        sh:path schema:name ;
        sh:name "Full Name" ;
        sh:description "Full chemical name of the battery chemistry." ;
        sh:node dpp-sh:TranslatableText
    ] ;
    sh:property [
        sh:path eubat:cathodeActiveMaterial ;
        sh:datatype xsd:string
    ] ;
    sh:property [
        sh:path eubat:anodeActiveMaterial ;
        sh:datatype xsd:string
    ] ;
    sh:property [
        sh:path eubat:electrolyteType ;
        sh:node dpp-sh:TranslatableText
    ] .

# -----------------------------------------------------------------------------
# Technical Specification Shape
# -----------------------------------------------------------------------------
eubat:TechnicalSpecificationShape a sh:NodeShape ;
    sh:targetClass eubat:TechnicalSpecification ;
    rdfs:label "Technical Specification Shape" ;
    sh:property [
        sh:path eubat:hasRatedCapacity ;
        sh:name "Rated Capacity" ;
        sh:class gs1:QuantitativeValue
    ] ;
    sh:property [
        sh:path eubat:hasRatedEnergy ;
        sh:name "Rated Energy" ;
        sh:class gs1:QuantitativeValue
    ] ;
    sh:property [
        sh:path eubat:hasNominalVoltage ;
        sh:name "Nominal Voltage" ;
        sh:class gs1:QuantitativeValue
    ] ;
    sh:property [
        sh:path eubat:expectedCycleLife ;
        sh:datatype xsd:integer
    ] ;
    sh:property [
        sh:path eubat:roundTripEfficiency ;
        sh:datatype xsd:decimal
    ] .

# -----------------------------------------------------------------------------
# Battery Material Shape
# -----------------------------------------------------------------------------
eubat:BatteryMaterialShape a sh:NodeShape ;
    sh:targetClass eubat:BatteryMaterial ;
    rdfs:label "Battery Material Shape" ;
    sh:property [
        sh:path schema:name ;
        sh:node dpp-sh:TranslatableText ;
        sh:minCount 1
] ;
    sh:property [
        sh:path eubat:casNumber ;
        sh:name "CAS Number" ;
        sh:datatype xsd:string
    ] ;
    sh:property [
        sh:path eubat:hasComponentLocation ;
        sh:class eubat:ComponentLocation
    ] ;
    sh:property [
        sh:path schema:weightPercentage ;
        sh:datatype xsd:decimal
    ] ;
    sh:property [
        sh:path eubat:isCriticalRawMaterial ;
        sh:datatype xsd:boolean
    ] .

# -----------------------------------------------------------------------------
# Recycled Content Shape
# -----------------------------------------------------------------------------
eubat:RecycledContentShape a sh:NodeShape ;
    sh:targetClass eubat:RecycledContent ;
    rdfs:label "Recycled Content Shape" ;
    sh:property [
        sh:path eubat:lithiumRecycledShare ;
        sh:datatype xsd:decimal
    ] ;
    sh:property [
        sh:path eubat:cobaltRecycledShare ;
        sh:datatype xsd:decimal
    ] ;
    sh:property [
        sh:path eubat:nickelRecycledShare ;
        sh:datatype xsd:decimal
    ] ;
    sh:property [
        sh:path eubat:leadRecycledShare ;
        sh:datatype xsd:decimal
    ] .

# -----------------------------------------------------------------------------
# End of Life Info Shape
# -----------------------------------------------------------------------------
eubat:EndOfLifeInfoShape a sh:NodeShape ;
    sh:targetClass eubat:EndOfLifeInfo ;
    rdfs:label "End of Life Information Shape" ;
    sh:property [
        sh:path eubat:hasDismantlingInstructions ;
        sh:nodeKind sh:IRI
    ] ;
    sh:property [
        sh:path eubat:extinguishingAgent ;
        sh:node dpp-sh:TranslatableText
    ] .

# -----------------------------------------------------------------------------
# Hazardous Substance Shape
# -----------------------------------------------------------------------------
eubat:HazardousSubstanceShape a sh:NodeShape ;
    sh:targetClass eubat:HazardousSubstance ;
    rdfs:label "Hazardous Substance Shape" ;
    sh:property [
        sh:path schema:name ;
        sh:node dpp-sh:TranslatableText ;
        sh:minCount 1
] ;
    sh:property [
        sh:path eubat:substanceCasNumber ;
        sh:datatype xsd:string
    ] ;
    sh:property [
        sh:path eubat:hasHazardClass ;
        sh:class eubat:HazardClass
    ] ;
    sh:property [
        sh:path eubat:concentration ;
        sh:datatype xsd:decimal
    ] .

# -----------------------------------------------------------------------------
# Operator Information Shape
# -----------------------------------------------------------------------------
eubat:OperatorInformationShape a sh:NodeShape ;
    sh:targetClass oec:OperatorInformation ;
    rdfs:label "Operator Information Shape" ;
    rdfs:comment "Battery-specific constraint on the shared oec:OperatorInformation class, which is the declared range of eubat:hasOperatorInformation. There is no eubat:OperatorInformation class." ;
    # An object property: the role is an oec: individual, not a string. The shared
    # dpp-sh:OperatorInformationShape pins the permitted values with sh:in; here it
    # is enough to require an IRI so a bare code cannot slip through.
    sh:property [
        sh:path eubat:hasOperatorRole ;
        sh:nodeKind sh:IRI
    ] .

# -----------------------------------------------------------------------------
# Supply Chain Due Diligence Shape
# -----------------------------------------------------------------------------
eubat:SupplyChainDueDiligenceShape a sh:NodeShape ;
    sh:targetClass eubat:SupplyChainDueDiligence ;
    rdfs:label "Supply Chain Due Diligence Shape" ;
    sh:property [
        sh:path eubat:dueDiligenceReportUrl ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1
    ] ;
    sh:property [
        sh:path eubat:thirdPartyAssurancesUrl ;
        sh:datatype xsd:anyURI
    ] .

# =============================================================================
# QuantitativeValue unitCode Shapes (GS1-idiomatic)
# =============================================================================
# Pin gs1:unitCode to the canonical UN/CEFACT Rec 20 code documented for each
# battery property. These complement dpp-sh:QuantitativeValueShape (which
# enforces value+unitCode presence); here we add the per-property hasValue
# check. Extend with more shapes as additional regulated properties are added.

eubat:RatedCapacityShape a sh:NodeShape ;
    sh:targetSubjectsOf eubat:hasRatedCapacity ;
    sh:property [
        sh:path ( eubat:hasRatedCapacity gs1:unitCode ) ;
        sh:hasValue "AMH" ;
        sh:message "eubat:hasRatedCapacity must use unitCode 'AMH' (ampere-hours)"@en
    ] .

eubat:RatedEnergyShape a sh:NodeShape ;
    sh:targetSubjectsOf eubat:hasRatedEnergy ;
    sh:property [
        sh:path ( eubat:hasRatedEnergy gs1:unitCode ) ;
        sh:hasValue "KWH" ;
        sh:message "eubat:hasRatedEnergy must use unitCode 'KWH' (kilowatt-hours)"@en
    ] .

eubat:NominalVoltageShape a sh:NodeShape ;
    sh:targetSubjectsOf eubat:hasNominalVoltage ;
    sh:property [
        sh:path ( eubat:hasNominalVoltage gs1:unitCode ) ;
        sh:hasValue "VLT" ;
        sh:message "eubat:hasNominalVoltage must use unitCode 'VLT' (volts)"@en
    ] .

eubat:RatedMaximumPowerShape a sh:NodeShape ;
    sh:targetSubjectsOf eubat:hasRatedMaximumPower ;
    sh:property [
        sh:path ( eubat:hasRatedMaximumPower gs1:unitCode ) ;
        sh:hasValue "KWT" ;
        sh:message "eubat:hasRatedMaximumPower must use unitCode 'KWT' (kilowatts)"@en
    ] .

# =============================================================================
# Granularity-dependent obligations (EN 18223 model / batch / item)
# =============================================================================
#
# Which identifiers a battery passport MUST carry depends on its granularity, so
# these obligations cannot live in eubat:BatteryShape:
#
#   * A model passport (GS1 Digital Link /01/ only) has no serial number. A
#     sh:minCount 1 on gs1:hasSerialNumber makes every model passport
#     non-conformant by construction.
#   * A batch or item passport does NOT restate the model- and party-level
#     identifiers. Per the granularity model a finer passport RESOLVES that data
#     up the Digital Link hierarchy — the same principle
#     scripts/lib/ec-readiness-shacl-core.ts implements by folding the three
#     levels onto one focus node before validating. Requiring them at item level
#     would demand duplication the architecture explicitly rejects.
#
# Mechanism: every shape below ships `sh:deactivated true` and the runner
# activates exactly ONE level by its IRI suffix (-model / -batch / -item), the
# same activateBySuffix() contract the generated EC readiness category shapes
# use. scripts/check-shapes.ts and scripts/build-gitb-resources.ts share it, so
# each level also becomes its own GITB validation type.
#
# Do NOT drop the sh:deactivated triples here: with all three active at once a
# passport would have to be a model AND an item simultaneously.

eubat:BatteryShape-model
    a sh:NodeShape ;
    sh:targetClass eubat:Battery ;
    rdfs:label "Battery Shape (model granularity)" ;
    rdfs:comment "Obligations of a model-level battery passport: the model- and party-level identifiers are declared here and resolved from here by finer passports." ;
    sh:deactivated true ;
    sh:property [
        sh:path eubat:facilityIdentifier ;
        sh:minCount 1 ;
        sh:message "A model-level battery passport must declare eubat:facilityIdentifier (BatteryPass-Ready v1.3 #11)."@en
    ] ;
    sh:property [
        sh:path eubat:operatorIdentifier ;
        sh:minCount 1 ;
        sh:message "A model-level battery passport must declare eubat:operatorIdentifier (BatteryPass-Ready v1.3 #9)."@en
    ] ;
    sh:property [
        sh:path eubat:manufacturerIdentifier ;
        sh:minCount 1 ;
        sh:message "A model-level battery passport must declare eubat:manufacturerIdentifier (BatteryPass-Ready v1.3 #10)."@en
    ] ;
    sh:property [
        sh:path gs1:manufacturer ;
        sh:minCount 1 ;
        sh:message "A model-level battery passport must reference its manufacturer (BatteryPass-Ready v1.3 #13)."@en
    ] .

eubat:BatteryShape-batch
    a sh:NodeShape ;
    sh:targetClass eubat:Battery ;
    rdfs:label "Battery Shape (batch granularity)" ;
    rdfs:comment "Obligations of a batch/lot-level battery passport. Model- and party-level identifiers are resolved from the model passport, not restated." ;
    sh:deactivated true .
# Deliberately carries no additional sh:property. A batch passport's lot identity
# IS its GS1 Digital Link (AI 10) rather than a separate property — there is no
# eubat:lotNumber, and minting one would duplicate the identifier that the
# document IRI already carries. The model/batch/item consistency of that IRI is
# checked by dpp-sh:GranularityDigitalLinkConstraint in dpp-core-shapes.ttl.
# The shape exists so that "eu.battery.batch" is a declarable validation type and
# so batch-specific obligations have an obvious home when the standards add them.

eubat:BatteryShape-item
    a sh:NodeShape ;
    sh:targetClass eubat:Battery ;
    rdfs:label "Battery Shape (item granularity)" ;
    rdfs:comment "Obligations of an item/serial-level battery passport. Model- and party-level identifiers are resolved up the Digital Link hierarchy, not restated." ;
    sh:deactivated true ;
    sh:property [
        sh:path gs1:hasSerialNumber ;
        sh:minCount 1 ;
        sh:message "An item-level battery passport must declare gs1:hasSerialNumber (BatteryPass-Ready v1.3 #8; EN 18223 item granularity, GS1 AI 21)."@en
    ] .
