@prefix : <https://ontology.inferal.com/modules/advertising-entity/> .
@prefix adm: <https://ontology.inferal.com/modules/advertising-measurement/> .
@prefix ci: <https://ontology.inferal.com/modules/company-intelligence/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@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#> .

:AdvertisingResourceShape
    a sh:NodeShape ;
    rdfs:label "advertising resource shape" ;
    rdfs:comment "Every shared advertising resource is a stable IRI. Configured tiers carry provider identity through the advertising entity shape; creatives are validated separately because a provider may expose one identifier for a folded ad-and-creative record." ;
    sh:targetClass :AdvertisingResource ;
    sh:nodeKind sh:IRI .

:AdvertisingEntityShape
    a sh:NodeShape ;
    rdfs:label "advertising entity shape" ;
    rdfs:comment "Every configured hierarchy tier must be identifiable on at least one channel. A tier with no provider identity cannot be joined to the metrics reported against it, which makes it unusable rather than merely incomplete." ;
    sh:targetClass :AdvertisingEntity ;
    sh:property [
        sh:path :identifiedBy ;
        sh:name "identified by" ;
        sh:description "At least one. Repeatable, because a measurement platform issues its own identifier for an entity another platform runs." ;
        sh:minCount 1 ;
        sh:class :ProviderIdentity ;
    ] ;
    sh:property [
        sh:path :entityName ;
        sh:name "entity name" ;
        sh:description "Optional: some tiers carry no advertiser-supplied name." ;
        sh:datatype xsd:string ;
    ] .

:ProviderIdentityShape
    a sh:NodeShape ;
    rdfs:label "provider identity shape" ;
    rdfs:comment "Both halves are required. A value without its channel does not identify anything, because platforms reuse numeric ranges and one platform's campaign identifier is another's account identifier." ;
    sh:targetClass :ProviderIdentity ;
    sh:property [
        sh:path :identifierValue ;
        sh:name "identifier value" ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:datatype xsd:string ;
        sh:minLength 1 ;
    ] ;
    sh:property [
        sh:path :issuingChannel ;
        sh:name "issuing channel" ;
        sh:description "Mandatory. This is the half the vocabulary being replaced omits, which is why a join on its shared identifier predicate conflates unrelated entities." ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:class adm:Channel ;
    ] .

:AdAccountShape
    a sh:NodeShape ;
    rdfs:label "ad account shape" ;
    rdfs:comment "An account exists at exactly one channel and is billed in one currency. Its holder is a role rather than a property, so an agency holding accounts for several clients is expressible." ;
    sh:targetClass :AdAccount ;
    sh:property [
        sh:path :accountChannel ;
        sh:name "account channel" ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:class adm:Channel ;
    ] ;
    sh:property [
        sh:path :accountCurrency ;
        sh:name "account currency" ;
        sh:description "Three uppercase letters, per ISO 4217. Metrics rows generally omit the currency, so this is where a lowering resolves it from." ;
        sh:maxCount 1 ;
        sh:datatype xsd:string ;
        sh:pattern "^[A-Z]{3}$" ;
    ] ;
    sh:property [
        sh:path :heldUnderRole ;
        sh:name "held under role" ;
        sh:description "Repeatable and time-bounded: holders change, and the history matters for attributing past spend to whoever was responsible for it." ;
        sh:class ci:Role ;
    ] .

:CampaignShape
    a sh:NodeShape ;
    rdfs:label "campaign shape" ;
    rdfs:comment "A campaign may carry one reported objective. Absence means the provider did not report one; it must never be filled with an invented unknown value. Tier classification comes from the provider role mapping, while the targeting tier remains forbidden from carrying an objective." ;
    sh:targetClass :Campaign ;
    sh:property [
        sh:path :pursuesBidStrategy ;
        sh:name "pursues bid strategy" ;
        sh:description "Optional because a provider may not report a bidding rule. When present it is single-valued and must be a governed bid-strategy IRI." ;
        sh:maxCount 1 ;
        sh:class :BidStrategy ;
    ] ;
    sh:property [
        sh:path :pursuesObjective ;
        sh:name "pursues objective" ;
        sh:description "Optional because some provider campaign records expose no objective. When present it is single-valued and must be a governed objective IRI." ;
        sh:maxCount 1 ;
        sh:class :Objective ;
    ] .

:TargetingGroupShape
    a sh:NodeShape ;
    rdfs:label "targeting group shape" ;
    rdfs:comment "The middle tier carries no objective: that belongs to the campaign above it. Forbidding one here is the constraint that keeps the two tiers apart when a provider names them confusingly." ;
    sh:targetClass :TargetingGroup ;
    sh:property [
        sh:path :pursuesObjective ;
        sh:name "pursues objective" ;
        sh:description "Forbidden. A tier that sets an objective is the campaign tier however it is labelled." ;
        sh:maxCount 0 ;
    ] ;
    sh:property [
        sh:path :bidAmount ;
        sh:name "bid amount" ;
        sh:description "At most one, reusing the shared monetary amount so a bid and the spend it produces compare directly." ;
        sh:maxCount 1 ;
        sh:class adm:MonetaryAmount ;
    ] .

:AdShape
    a sh:NodeShape ;
    rdfs:label "ad shape" ;
    rdfs:comment "An ad delivers at least one creative and may deliver several, because a provider can rotate independently identified variants or assemble a dynamic asset set under one ad. Several ads may deliver the same creative, which is why the creative is a separate resource and not a property of the ad." ;
    sh:targetClass :Ad ;
    sh:property [
        sh:path :deliversCreative ;
        sh:name "delivers creative" ;
        sh:description "Repeatable. A single value is the common case, and a rotating or dynamically assembled ad carries one value per independently identified variant." ;
        sh:minCount 1 ;
        sh:class :Creative ;
    ] .

:CreativeShape
    a sh:NodeShape ;
    rdfs:label "creative shape" ;
    rdfs:comment "A creative is an asset rather than a configured position, so it carries no channel and no identity requirement of its own. It must be a resolvable IRI so several ads can reference the same one." ;
    sh:targetClass :Creative ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :accountChannel ;
        sh:name "account channel" ;
        sh:description "Forbidden: a creative occupies no position in the account hierarchy." ;
        sh:maxCount 0 ;
    ] ;
    sh:property [
        sh:path :creativeFormat ;
        sh:name "creative format" ;
        sh:description "At most one. Format decides which metrics apply: video view counts mean nothing for a static image." ;
        sh:maxCount 1 ;
        sh:class :CreativeFormat ;
    ] .

:CreativeFormatShape
    a sh:NodeShape ;
    rdfs:label "creative format shape" ;
    rdfs:comment "Formats must be scheme-attached resolvable IRIs so a rule selecting video creatives branches on identity rather than parsing a label, and so a profile can extend the scheme." ;
    sh:targetClass :CreativeFormat ;
    sh:nodeKind sh:IRI .

:ObjectiveShape
    a sh:NodeShape ;
    rdfs:label "objective shape" ;
    rdfs:comment "Objectives must be scheme-attached resolvable IRIs so a rule can branch on identity and a profile can extend the scheme." ;
    sh:targetClass :Objective ;
    sh:nodeKind sh:IRI .

:BidStrategyShape
    a sh:NodeShape ;
    rdfs:label "bid strategy shape" ;
    rdfs:comment "Bid strategies must be scheme-attached resolvable IRIs so a rule can branch on identity and a profile can extend the scheme with values only it reports." ;
    sh:targetClass :BidStrategy ;
    sh:nodeKind sh:IRI .
