@prefix : <https://ontology.inferal.com/modules/advertising-delivery-state/> .
@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix rng: <https://ontology.inferal.com/modules/range/> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

:DeliveryStateShape
    a sh:NodeShape ;
    rdfs:label "delivery state shape" ;
    rdfs:comment "A state identifies its entity, advertiser intent and lifecycle standing separately. Observed serving is optional: absence means unknown, not non-serving. Reported holds do not establish actual delivery." ;
    sh:targetClass :DeliveryState ;
    sh:property [
        sh:path :stateOf ;
        sh:name "state of" ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
    ] ;
    sh:property [
        sh:path :intendedDelivery ;
        sh:name "intended delivery" ;
        sh:description "Mandatory. Without it, an entity prevented from delivering is indistinguishable from one the advertiser switched off." ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:class :DeliveryIntent ;
    ] ;
    sh:property [
        sh:path :observedServing ;
        sh:name "observed serving" ;
        sh:description "Optional legacy provider permission report in the producer scope. Absence leaves that report unknown; no value establishes measured delivery or complete cross-provider eligibility." ;
        sh:maxCount 1 ;
        sh:class :ServingState ;
    ] ;
    sh:property [
        sh:path :lifecycleStanding ;
        sh:name "lifecycle standing" ;
        sh:description "Mandatory and separate from intent, so a query for switched-off entities does not return deleted ones. The value is one of the three standings this module defines, or a profile concept narrower than exactly one of them, such as a draft narrower than live. Every standing therefore rolls up to one shared value, and a comparability rule written for the three still accounts for a provider's finer standing." ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:class :LifecycleState ;
        sh:node :LifecycleStandingValueShape ;
    ] ;
    sh:property [
        sh:path :heldBy ;
        sh:name "held by" ;
        sh:description "Optional and repeatable: several independent causes can prevent delivery at once." ;
        sh:class :ServingHold ;
    ] ;
    sh:property [
        sh:path :effectiveDuring ;
        sh:name "effective during" ;
        sh:maxCount 1 ;
        sh:class rng:Range ;
    ] .

:ServingHoldShape
    a sh:NodeShape ;
    rdfs:label "serving hold shape" ;
    rdfs:comment "A hold states its kind and where it came from. Both are required: the kind decides who can act, and the origin is what makes an inherited pause traceable to the ancestor responsible instead of leaving an operator to walk the hierarchy." ;
    sh:targetClass :ServingHold ;
    sh:property [
        sh:path :holdKind ;
        sh:name "hold kind" ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:class :HoldKind ;
    ] ;
    sh:property [
        sh:path :originatingResource ;
        sh:name "originating resource" ;
        sh:description "Mandatory. A hold with no origin cannot be acted on, which is the defect in provider enumerations whose values say an ancestor is paused without naming it." ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
    ] .

:DeliveryIntentShape
    a sh:NodeShape ;
    rdfs:label "delivery intent shape" ;
    rdfs:comment "Intents must be resolvable IRIs so a rule branches on identity rather than a label." ;
    sh:targetClass :DeliveryIntent ;
    sh:nodeKind sh:IRI .

:ServingStateShape
    a sh:NodeShape ;
    rdfs:label "serving state shape" ;
    rdfs:comment "Serving states must be resolvable IRIs for the same reason as intents." ;
    sh:targetClass :ServingState ;
    sh:nodeKind sh:IRI .

:HoldKindShape
    a sh:NodeShape ;
    rdfs:label "hold kind shape" ;
    rdfs:comment "Hold kinds are extended by provider profiles, so being a resolvable IRI keeps the vocabulary navigable." ;
    sh:targetClass :HoldKind ;
    sh:nodeKind sh:IRI .

:LifecycleStateShape
    a sh:NodeShape ;
    rdfs:label "lifecycle state shape" ;
    rdfs:comment "Lifecycle values must be resolvable IRIs. Which values a delivery state may stand in is decided on the standing, by the lifecycle standing value shape, so a provider profile can still declare a concept carrying its own token." ;
    sh:targetClass :LifecycleState ;
    sh:nodeKind sh:IRI .

:LifecycleStandingValueShape
    a sh:NodeShape ;
    rdfs:label "lifecycle standing value shape" ;
    rdfs:comment "A standing is live, archived or removed, or a concept whose single broader concept is one of the three. A standing with no broader shared standing, with another kind of broader concept, or with two broader standings is refused, because no comparability rule could say which shared standing it counts as." ;
    sh:or (
        [ sh:in ( :liveEntity :archivedEntity :removedEntity ) ]
        [
            sh:path skos:broader ;
            sh:minCount 1 ;
            sh:maxCount 1 ;
            sh:in ( :liveEntity :archivedEntity :removedEntity ) ;
        ]
    ) .


:DeliveryEvidenceObservationShape
    a sh:NodeShape ;
    rdfs:label "delivery evidence observation shape" ;
    rdfs:comment "Validates the selected v1 headers and independent resolved or unresolved evidence axes without requiring a complete legacy delivery state." ;
    sh:targetClass :DeliveryEvidenceObservation ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :aboutEntity ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
    ] ;
    sh:property [
        sh:path :observedAt ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:datatype xsd:dateTimeStamp ;
    ] ;
    sh:property [
        sh:path :producerContract ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ; sh:in ( <https://ontology.inferal.com/contracts/advertising/delivery-evidence/v1> ) ;
    ] ;
    sh:property [
        sh:path :reportedChannel ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
    ] ;
    sh:property [
        sh:path :eligibilityEvidence ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ; sh:in ( :resolvedEvidence :missingEvidence :unassessedEvidence :conflictingEvidence ) ;
    ] ;
    sh:property [
        sh:path :servingEvidence ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ; sh:in ( :resolvedEvidence :missingEvidence :unassessedEvidence :conflictingEvidence ) ;
    ] ;
    sh:property [
        sh:path :reportedEligibility ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ; sh:in ( :eligibleUnderNativeCondition :ineligibleUnderNativeCondition ) ;
    ] ;
    sh:property [
        sh:path :eligibilityScope ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
    ] ;
    sh:property [
        sh:path :providerReportedServing ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ; sh:in ( :reportedServing :reportedLimitedServing :reportedNotServing ) ;
    ] ;
    sh:property [
        sh:path :reportedIntent ;
        sh:maxCount 1 ;
        sh:class :DeliveryIntent ; sh:nodeKind sh:IRI ;
    ] ;
    sh:property [
        sh:path :reportedLifecycle ;
        sh:maxCount 1 ;
        sh:class :LifecycleState ; sh:nodeKind sh:IRI ;
    ] ;
    sh:property [
        sh:path :controlObservation ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
    ] ;
    sh:property [
        sh:path :manualPaused ;
        sh:maxCount 1 ;
        sh:datatype xsd:boolean ;
    ] ;
    sh:property [
        sh:path :budgetPaused ;
        sh:maxCount 1 ;
        sh:datatype xsd:boolean ;
    ] ;
    sh:property [ sh:path prov:wasDerivedFrom ; sh:minCount 1 ; sh:nodeKind sh:IRI ] ;
    sh:property [ sh:path :reportedHold ; sh:class :ServingHold ; sh:nodeKind sh:IRI ] ;
    sh:property [ sh:path :sourceStatus ; sh:datatype xsd:string ] ;
    sh:property [ sh:path :sourcePrimaryStatus ; sh:datatype xsd:string ] ;
    sh:property [ sh:path :sourceServingStatus ; sh:datatype xsd:string ] ;
    sh:property [ sh:path :sourceEffectiveStatus ; sh:datatype xsd:string ] ;
    sh:property [ sh:path :sourceAccountStatus ; sh:datatype xsd:string ] ;
    sh:sparql [
        sh:message "Resolved eligibility requires one factual value and one native scope; unresolved evidence supplies neither." ;
        sh:select """
            PREFIX adel: <https://ontology.inferal.com/modules/advertising-delivery-state/>
            SELECT $this ?path WHERE {
              { $this adel:eligibilityEvidence adel:resolvedEvidence .
                FILTER NOT EXISTS { $this adel:reportedEligibility ?value }
                BIND(adel:reportedEligibility AS ?path) }
              UNION
              { $this adel:eligibilityEvidence adel:resolvedEvidence .
                FILTER NOT EXISTS { $this adel:eligibilityScope ?scope }
                BIND(adel:eligibilityScope AS ?path) }
              UNION
              { $this adel:eligibilityEvidence ?evidence ; adel:reportedEligibility ?value .
                FILTER(?evidence != adel:resolvedEvidence)
                BIND(adel:reportedEligibility AS ?path) }
              UNION
              { $this adel:eligibilityEvidence ?evidence ; adel:eligibilityScope ?scope .
                FILTER(?evidence != adel:resolvedEvidence)
                BIND(adel:eligibilityScope AS ?path) }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "A provider serving report requires resolved serving evidence; unresolved evidence supplies no factual serving value." ;
        sh:select """
            PREFIX adel: <https://ontology.inferal.com/modules/advertising-delivery-state/>
            SELECT $this ?path WHERE {
              { $this adel:servingEvidence adel:resolvedEvidence .
                FILTER NOT EXISTS { $this adel:providerReportedServing ?value }
                BIND(adel:providerReportedServing AS ?path) }
              UNION
              { $this adel:servingEvidence ?evidence ; adel:providerReportedServing ?value .
                FILTER(?evidence != adel:resolvedEvidence)
                BIND(adel:providerReportedServing AS ?path) }
            }
        """ ;
    ] .
