@prefix : <https://ontology.inferal.com/modules/advertising-measurement/> .
@prefix ex: <https://example.com/advertising-measurement/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix rng: <https://ontology.inferal.com/modules/range/> .
@prefix time: <http://www.w3.org/2006/time#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

# Worked instances for the Advertising Measurement module.
#
# Four scenarios:
#   A. The same metric reported by three providers, comparable and summable.
#   B. Two observations of the same subject that must NOT be summed, because
#      their attribution bases differ.
#   C. An ad that moves between ad groups partway through a reporting window,
#      which is what makes time-bounded membership necessary.
#   D. Placement stats: a total and the slices that partition it, which must not
#      be summed together, plus a two-dimension slice that is comparable with
#      neither.


# --- Temporal instants -------------------------------------------------------

ex:t-2026-06-01
    a time:Instant ;
    rdfs:label "2026-06-01T00:00:00Z" ;
    time:inXSDDateTimeStamp "2026-06-01T00:00:00Z"^^xsd:dateTimeStamp .

ex:t-2026-06-16
    a time:Instant ;
    rdfs:label "2026-06-16T00:00:00Z" ;
    time:inXSDDateTimeStamp "2026-06-16T00:00:00Z"^^xsd:dateTimeStamp .

ex:t-2026-07-01
    a time:Instant ;
    rdfs:label "2026-07-01T00:00:00Z" ;
    time:inXSDDateTimeStamp "2026-07-01T00:00:00Z"^^xsd:dateTimeStamp .


# --- Reporting window --------------------------------------------------------

ex:june-2026
    a :ReportingWindow ;
    rdfs:label "June 2026" ;
    rdfs:comment "Half-open month window: includes 1 June, excludes 1 July." ;
    :windowStart ex:t-2026-06-01 ;
    :windowEnd ex:t-2026-07-01 ;
    rng:lowerInclusive true ;
    rng:upperInclusive false .


# --- Attribution bases -------------------------------------------------------

ex:sevenDayLookback
    a rng:Range ;
    rdfs:label "seven day lookback" ;
    rdfs:comment "The interval before an outcome within which a prior interaction can receive credit." .

ex:lastClick-7d
    a :AttributionBasis ;
    rdfs:label "last click, 7 day lookback" ;
    :usedAttributionModel :lastClickModel ;
    :lookbackWindow ex:sevenDayLookback .

ex:proprietary-7d
    a :AttributionBasis ;
    rdfs:label "provider proprietary, 7 day lookback" ;
    rdfs:comment "Same lookback as ex:lastClick-7d, different credit rule. Observations under the two bases are not comparable despite agreeing on subject, kind, grain, and window." ;
    :usedAttributionModel :providerProprietaryModel ;
    :lookbackWindow ex:sevenDayLookback .


# --- Scenario A: one metric, three providers, comparable ---------------------
#
# Meta reports spend as a minor-unit string, Google as a micros integer, and
# LinkedIn as a decimal string. All three lower to a decimal in major units, so
# a cross-provider total is well defined. Each states the same window, grain,
# and attribution basis, which is what licenses the sum.

ex:meta-spend-june
    a :MetricObservation ;
    rdfs:label "Meta campaign spend, June 2026" ;
    :observationSubject ex:meta-campaign-1 ;
    :observationGrain :CampaignGrain ;
    :metricKind :spend ;
    :reportingWindow ex:june-2026 ;
    :observedValue ex:meta-spend-june-value ;
    :observedChannel :metaChannel .

ex:meta-spend-june-value
    a :MonetaryValue ;
    rdfs:comment "Wire form was the string \"1250050\" in minor units." ;
    :canonicalMagnitude "12500.50"^^xsd:decimal ;
    :valueAmount ex:meta-spend-june-amount .

ex:meta-spend-june-amount
    a :MonetaryAmount ;
    :amountValue "12500.50"^^xsd:decimal ;
    :currencyCode "USD" .

ex:googleads-spend-june
    a :MetricObservation ;
    rdfs:label "Google Ads campaign spend, June 2026" ;
    :observationSubject ex:googleads-campaign-1 ;
    :observationGrain :CampaignGrain ;
    :metricKind :spend ;
    :reportingWindow ex:june-2026 ;
    :observedValue ex:googleads-spend-june-value ;
    :observedChannel :googleAdsChannel .

ex:googleads-spend-june-value
    a :MonetaryValue ;
    rdfs:comment "Wire form was the integer 8200000000 in micros." ;
    :canonicalMagnitude "8200.00"^^xsd:decimal ;
    :valueAmount ex:googleads-spend-june-amount .

ex:googleads-spend-june-amount
    a :MonetaryAmount ;
    :amountValue "8200.00"^^xsd:decimal ;
    :currencyCode "USD" .

ex:linkedin-spend-june
    a :MetricObservation ;
    rdfs:label "LinkedIn campaign spend, June 2026" ;
    rdfs:comment "Subject is LinkedIn's CampaignGroup, which is its budget and schedule tier, so the grain is campaign rather than ad group." ;
    :observationSubject ex:linkedin-campaigngroup-1 ;
    :observationGrain :CampaignGrain ;
    :metricKind :spend ;
    :reportingWindow ex:june-2026 ;
    :observedValue ex:linkedin-spend-june-value ;
    :observedChannel :linkedinChannel .

ex:linkedin-spend-june-value
    a :MonetaryValue ;
    rdfs:comment "Wire form was the decimal string \"3410.25\"." ;
    :canonicalMagnitude "3410.25"^^xsd:decimal ;
    :valueAmount ex:linkedin-spend-june-amount .

ex:linkedin-spend-june-amount
    a :MonetaryAmount ;
    :amountValue "3410.25"^^xsd:decimal ;
    :currencyCode "USD" .


# --- Scenario B: same subject, incomparable bases ----------------------------
#
# Both observations describe conversions on the same campaign over the same
# window at the same grain. They differ only in attribution basis, and therefore
# must never be added or compared. In the current ads vocabulary these two rows
# are indistinguishable.

ex:meta-conversions-lastclick
    a :MetricObservation ;
    rdfs:label "Meta conversions under last click, June 2026" ;
    :observationSubject ex:meta-campaign-1 ;
    :observationGrain :CampaignGrain ;
    :metricKind :conversions ;
    :reportingWindow ex:june-2026 ;
    :observedValue ex:meta-conversions-lastclick-value ;
    :attributionBasis ex:lastClick-7d ;
    :observedChannel :metaChannel .

ex:meta-conversions-lastclick-value
    a :CountValue ;
    :canonicalMagnitude "412"^^xsd:decimal .

ex:meta-conversions-proprietary
    a :MetricObservation ;
    rdfs:label "Meta conversions under the provider model, June 2026" ;
    rdfs:comment "Fractional because the proprietary model apportions credit across touchpoints." ;
    :observationSubject ex:meta-campaign-1 ;
    :observationGrain :CampaignGrain ;
    :metricKind :conversions ;
    :reportingWindow ex:june-2026 ;
    :observedValue ex:meta-conversions-proprietary-value ;
    :attributionBasis ex:proprietary-7d ;
    :observedChannel :metaChannel .

ex:meta-conversions-proprietary-value
    a :CountValue ;
    :canonicalMagnitude "587.4"^^xsd:decimal .


# --- Scenario C: an ad that moves between ad groups --------------------------
#
# ex:meta-ad-1 sits in ad group A for the first half of June and in ad group B
# for the second half. Its June observation therefore cannot be rolled up to a
# single ad group. A model that stores the parent as a direct property records
# only the current parent and silently attributes the whole month to B.

ex:meta-ad-1-in-group-a
    a :HierarchyMembership ;
    rdfs:label "ad 1 in ad group A, 1 to 16 June 2026" ;
    :containedResource ex:meta-ad-1 ;
    :containedIn ex:meta-adgroup-a ;
    :validityWindow ex:validity-first-half-june .

ex:validity-first-half-june
    a rng:Range ;
    rdfs:label "1 to 16 June 2026" ;
    rng:lowerBound ex:t-2026-06-01 ;
    rng:upperBound ex:t-2026-06-16 ;
    rng:lowerInclusive true ;
    rng:upperInclusive false .

ex:meta-ad-1-in-group-b
    a :HierarchyMembership ;
    rdfs:label "ad 1 in ad group B, from 16 June 2026" ;
    rdfs:comment "Open above: the containment is current." ;
    :containedResource ex:meta-ad-1 ;
    :containedIn ex:meta-adgroup-b ;
    :validityWindow ex:validity-second-half-june .

ex:validity-second-half-june
    a rng:Range ;
    rdfs:label "from 16 June 2026" ;
    rng:lowerBound ex:t-2026-06-16 ;
    rng:lowerInclusive true .

# A stable membership, for contrast: this ad never moved.
ex:meta-ad-2-in-group-a
    a :HierarchyMembership ;
    rdfs:label "ad 2 in ad group A, from 1 June 2026" ;
    :containedResource ex:meta-ad-2 ;
    :containedIn ex:meta-adgroup-a ;
    :validityWindow ex:validity-from-june .

ex:validity-from-june
    a rng:Range ;
    rdfs:label "from 1 June 2026" ;
    rng:lowerBound ex:t-2026-06-01 ;
    rng:lowerInclusive true .

ex:meta-ad-1-impressions-june
    a :MetricObservation ;
    rdfs:label "Meta ad 1 impressions, June 2026" ;
    rdfs:comment "Spans the move, so this value belongs to neither ad group in full." ;
    :observationSubject ex:meta-ad-1 ;
    :observationGrain :AdGrain ;
    :metricKind :impressions ;
    :reportingWindow ex:june-2026 ;
    :observedValue ex:meta-ad-1-impressions-june-value ;
    :observedChannel :metaChannel .

ex:meta-ad-1-impressions-june-value
    a :CountValue ;
    :canonicalMagnitude "184203"^^xsd:decimal .


# --- Scenario D: placement stats ---------------------------------------------
#
# The same ad, metric, window, and attribution basis as
# ex:meta-ad-1-impressions-june above, but partitioned by placement. The total
# and its two slices describe one measure at two resolutions.
#
# Adding all three together counts every impression twice. Nothing in the
# current ads vocabulary can express the difference: a placement-level row and
# an ad-level row are both just "insights". Here the breakdown set is what
# separates them, and the double-count-risk query finds the pair.
#
# Placement is a breakdown dimension rather than a hierarchy tier because no
# advertiser owns a placement. It is where delivery happened, not a resource in
# the account structure, so it can never be an observation subject.

ex:placement-feed
    a :Breakdown ;
    rdfs:label "placement is feed" ;
    :breakdownDimension :placementDimension ;
    :breakdownValue :feedPlacement .

ex:placement-stories
    a :Breakdown ;
    rdfs:label "placement is stories" ;
    :breakdownDimension :placementDimension ;
    :breakdownValue :storiesPlacement .

ex:country-us
    a :Breakdown ;
    rdfs:label "country is US" ;
    :breakdownDimension :countryDimension ;
    :breakdownValue ex:country-code-us .

ex:country-code-us
    a rdfs:Resource ;
    rdfs:label "United States" .

ex:meta-ad-1-impressions-feed
    a :MetricObservation ;
    rdfs:label "Meta ad 1 impressions on feed, June 2026" ;
    :observationSubject ex:meta-ad-1 ;
    :observationGrain :AdGrain ;
    :metricKind :impressions ;
    :reportingWindow ex:june-2026 ;
    :observedValue ex:meta-ad-1-impressions-feed-value ;
    :observedChannel :metaChannel ;
    :observationBreakdown ex:placement-feed .

ex:meta-ad-1-impressions-feed-value
    a :CountValue ;
    :canonicalMagnitude "121440"^^xsd:decimal .

ex:meta-ad-1-impressions-stories
    a :MetricObservation ;
    rdfs:label "Meta ad 1 impressions on stories, June 2026" ;
    :observationSubject ex:meta-ad-1 ;
    :observationGrain :AdGrain ;
    :metricKind :impressions ;
    :reportingWindow ex:june-2026 ;
    :observedValue ex:meta-ad-1-impressions-stories-value ;
    :observedChannel :metaChannel ;
    :observationBreakdown ex:placement-stories .

ex:meta-ad-1-impressions-stories-value
    a :CountValue ;
    :canonicalMagnitude "62763"^^xsd:decimal .

# Sliced along two dimensions at once. Not comparable with either single-dimension
# slice above, because the breakdown sets differ.
ex:meta-ad-1-impressions-feed-us
    a :MetricObservation ;
    rdfs:label "Meta ad 1 impressions on feed in the US, June 2026" ;
    :observationSubject ex:meta-ad-1 ;
    :observationGrain :AdGrain ;
    :metricKind :impressions ;
    :reportingWindow ex:june-2026 ;
    :observedValue ex:meta-ad-1-impressions-feed-us-value ;
    :observedChannel :metaChannel ;
    :observationBreakdown ex:placement-feed , ex:country-us .

ex:meta-ad-1-impressions-feed-us-value
    a :CountValue ;
    :canonicalMagnitude "88012"^^xsd:decimal .


# --- Scenario E: a composite provider dimension ------------------------------
#
# Some providers offer a joined dimension whose values pre-combine two axes, so
# one wire value names both the property and the position within it. A value of
# that form cannot be filtered on either axis alone, which is the same defect as
# a compound class name: the parts are there but not separable.
#
# It lowers to one breakdown per axis rather than a single composite, so the
# result is queryable by property, by position, or by both.

ex:platform-instagram
    a :Breakdown ;
    rdfs:label "publisher platform is Instagram" ;
    :breakdownDimension :publisherPlatformDimension ;
    :breakdownValue :instagramPlatform .

ex:position-stories
    a :Breakdown ;
    rdfs:label "placement is stories" ;
    :breakdownDimension :placementDimension ;
    :breakdownValue :storiesPlacement .

ex:meta-ad-1-impressions-instagram-stories
    a :MetricObservation ;
    rdfs:label "Meta ad 1 impressions in Instagram stories, June 2026" ;
    rdfs:comment "Lowered from a single joined value naming both the property and the position. Two breakdowns, so a query can ask for all Instagram delivery or all stories delivery independently." ;
    :observationSubject ex:meta-ad-1 ;
    :observationGrain :AdGrain ;
    :metricKind :impressions ;
    :reportingWindow ex:june-2026 ;
    :observedValue ex:meta-ad-1-impressions-instagram-stories-value ;
    :observedChannel :metaChannel ;
    :observationBreakdown ex:platform-instagram , ex:position-stories .

ex:meta-ad-1-impressions-instagram-stories-value
    a :CountValue ;
    :canonicalMagnitude "41208"^^xsd:decimal .
