Skip to content

feat(schemas): vendor-metric extensions surface for measurement metrics#3492

Merged
bokelley merged 7 commits into
mainfrom
bokelley/vendor-metric-extensions
Apr 29, 2026
Merged

feat(schemas): vendor-metric extensions surface for measurement metrics#3492
bokelley merged 7 commits into
mainfrom
bokelley/vendor-metric-extensions

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Summary

Resolves the closed/open available-metric.json enum question raised in #3460. Adds a structured parallel surface for vendor-defined metrics rather than opening the standard vocabulary to free-form strings.

The motivating use case: a buyer that needs Adelaide attention, Scope3 emissions, Nielsen DAR demographics, IAS/DV custom quality, brand-lift surveys, or in-flight attention panels — none of which fit the closed standard enum, but all of which need structured discovery + reporting.

Why structured, not open-string

Opening the closed enum to x_* strings would solve the asymmetry with delivery-metrics.json's additionalProperties: true posture but defeats discovery: a buyer asking "I need attention measurement" can't query a flat string namespace where Adelaide writes x_adelaide_attention_units, Lumen writes x_lumen_attention_seconds, and TVision writes x_tv_co_view_attention. A structured extension gives the buyer two queryable axes — vendor (BrandRef) and metric_category (an industry classification) — with the standard metric_name as a third pin once vendors converge.

Schemas added

  • core/vendor-metric.json — descriptor: { vendor: BrandRef, metric_name, metric_category?, standard_reference? }. No agent_url — measurement-agent discovery defers to the vendor's brand.json agents[type='measurement'], matching the existing convention in measurement-terms.json and performance-standard.json (validated by both protocol expert and agentic architect on independent reviews).
  • core/vendor-metric-value.json — reported value: { vendor, metric_name, value, unit?, measurable_impressions?, breakdown? }. The measurable_impressions field is the coverage denominator — vendor measurement is rarely 100% (Adelaide only scores impressions where their SDK fires, Nielsen DAR only matches panel-resolved impressions, IAS/DV only measure where their tag is present). This pattern parallels the existing viewability.measurable_impressions field that has handled vendor coverage in the IAS/DV/MRC ecosystem for over a decade. The breakdown slot accommodates structured payloads beyond a single scalar (Nielsen demographic breakouts, TVision co-view, iSpot incremental decomposition).
  • enums/measurement-category.json — 9 values: attention, brand_lift, incrementality, audience, reach, creative_quality, emissions, outcomes, other. Tracks the established measurement-vendor space.

Wired in

  • core/reporting-capabilities.json → new vendor_metrics array (parallel to available_metrics)
  • core/product-filters.json → new required_vendor_metrics filter (each entry pins vendor, metric_name, metric_category, or any combination — at least one of three; AND across entries; cross-vendor query via metric_category; same filter-not-fail convention as siblings)
  • core/delivery-metrics.json → new vendor_metric_values array (parallel to standard scalars; additionalProperties: true preserved on parent for migration)
  • docs/media-buy/task-reference/get_products.mdx → new filter row
  • docs/media-buy/task-reference/get_media_buy_delivery.mdx → new bullet under per-package
  • docs/media-buy/media-buys/optimization-reporting.mdx → new Vendor-Defined Metrics section covering declaration, discovery, reporting, brand.json discovery anchor, the standards-driven promotion path, and v1 accountability scope

v1 accountability scope

Standard available_metrics are subject to the missing_metrics contract from #3472. Vendor metrics are advisory in v1 — buyers verify out-of-band via measurable_impressions coverage and direct calls to the vendor's measurement agent. The asymmetry reflects what the seller can credibly attest to: SSPs typically don't have Adelaide/Scope3 numbers in their delivery pipeline; those flow from the vendor's own infrastructure.

Promotion path

When the industry converges on a metric via a published standard (IAB Attention Measurement Guidelines, MRC variants, GARM emissions framework), the spec adds it to the closed available-metric.json enum and the vendor extensions become historical aliases. The standard_reference field anchors promotion to standards-body publications, not to ad-hoc vendor convergence counts.

Backwards compatibility

All additions are optional. Sellers without vendor metrics see no change. Closed available-metric.json enum unchanged. additionalProperties: true preserved on delivery-metrics.json so existing free-form vendor emissions remain conformant; structured vendor_metric_values is the recommended path going forward.

Pre-build expert review applied

Three experts reviewed the design before code was written. Key adjustments incorporated:

  • Dropped description and documentation_url from on-product extension (architect: 50K-token context bloat in a typical get_products response — moved to brand.json, fetched lazily)
  • currencyunit (free string accommodates score/seconds/persons/gCO2e/USD/etc. — product expert: single-currency field broke for non-monetary vendors)
  • Added breakdown (free-form object) for Nielsen DAR / TVision co-view / iSpot incremental
  • Added metric_category enum (architect + product expert: cross-vendor query via metric_name silently fails because vendors don't converge on names pre-standardization)
  • Added standard_reference URI (product expert: "3+ vendors converge → promote" isn't how MRC/IAB standards actually emerge — wire promotion to published standards instead)
  • Replaced parallel missing_vendor_metrics accountability array with measurable_impressions coverage on each value (per your prior note: missing-ness is a coverage question, not a binary, and viewability solved this a decade ago)

Test plan

  • npm run build:schemas — clean
  • npm run test:schemas — 7/7
  • npm run test:examples — 34/34
  • npm run test:json-schema — 255/255
  • npm run test:composed — 32/32
  • npm run typecheck — clean
  • npm run build:compliance — storyboard lints all green
  • precommit + pre-push — green

Deferred to follow-ups

Refs #3460. Closes the closed/open enum question.

🤖 Generated with Claude Code

bokelley and others added 7 commits April 28, 2026 20:51
Adds a structured parallel to the closed available-metric enum for
vendor-defined metrics (Adelaide attention, Scope3 emissions, Nielsen DAR
demographics, brand-lift surveys, in-flight attention panels). Resolves
the closed/open enum question raised in #3460 with a structured surface
instead of opening the standard vocabulary to free-form strings.

- core/vendor-metric.json declares the descriptor (vendor BrandRef,
  metric_name, optional metric_category, optional standard_reference).
- core/vendor-metric-value.json carries the reported value (vendor,
  metric_name, value, optional unit/measurable_impressions/breakdown).
  measurable_impressions is the coverage denominator following the
  decade-old viewability.measurable_impressions pattern.
- enums/measurement-category.json — 9-value IAB-style classification.
- reporting-capabilities.json adds vendor_metrics declaration array.
- product-filters.json adds required_vendor_metrics filter (vendor,
  metric_name, or metric_category pin per entry).
- delivery-metrics.json adds vendor_metric_values emission array.
- Docs updated across get_products, get_media_buy_delivery, and
  optimization-reporting.

No measurement_agent_url inlined — defers to brand.json
agents[type='measurement'] discovery, matching the convention already
established by measurement-terms.json and performance-standard.json.

Vendor metrics are advisory in v1 (no missing_vendor_metrics
accountability) — coverage is tracked via measurable_impressions.

Refs #3460.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per diff feedback: metric_category and standard_reference are properties
of the vendor's metric definition, not the seller's per-product
declaration. They live on the vendor's brand.json
(agents[type='measurement']) and are discovered out-of-band, not
duplicated on every seller's extension.

Schema changes:
- vendor-metric.json strips to {vendor, metric_name} only. No category,
  no standard_reference, no description, no documentation_url. Pointer-only.
- vendor-metric-value.json closed envelope (additionalProperties: false);
  breakdown is the single escape hatch for structured payloads.
- product-filters.json required_vendor_metrics drops metric_category;
  entries pin vendor and/or metric_name. Cross-vendor discovery is now
  the buyer agent's job (resolve via vendor brand.json).
- enums/measurement-category.json deleted (no longer referenced).
- reporting-capabilities.json drops uniqueItems on vendor_metrics
  (BrandRef optional fields defeat deep-equal); semantic uniqueness key
  documented as (vendor.domain, vendor.brand_id, metric_name).

Other expert-review fixes applied:
- measurable_impressions absence semantics: "treat as unspecified, do
  not compute coverage rate" (was "assume full coverage" — wrong vs
  IAS/DV/MRC viewability convention).
- required_vendor_metrics[].metric_name carries the same regex pattern
  as the schemas it filters.
- vendor_metric_values one-row-per-(vendor, metric_name)-per-period
  documented in delivery-metrics.json.
- All real brand names removed from schema examples; replaced with
  fictional placeholders (attentionvendor.example, emissionsvendor.example,
  panelmeasurement.example).
- Doc references to specific vendors generalized.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per diff feedback: metric_name is functionally an identifier (stable
lookup key within the vendor's namespace), not a display name. AdCP
convention is `_id` for stable identifiers (brand_id, format_id,
package_id, creative_id, media_buy_id); `_name` is reserved for
human-readable display strings.

- New core/vendor-metric-id.json shared schema (analogous to
  brand-id.json) with the regex pattern, bounds, and identifier
  semantics in one place.
- vendor-metric.json: metric_name → metric_id (refs vendor-metric-id).
- vendor-metric-value.json: metric_name → metric_id.
- product-filters.json required_vendor_metrics[].metric_name → metric_id.
- reporting-capabilities.json + delivery-metrics.json semantic-uniqueness
  keys updated: (vendor.domain, vendor.brand_id, metric_id).
- Docs and changeset updated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per diff feedback: vendor-metric.json wrapper carried only {vendor,
metric_id} — both required, both pulling from existing $refs. Two
fields with no fields-of-its-own to justify a dedicated schema file.
The wrapper added indirection without value.

The value side (vendor-metric-value.json) earns its own file: 6 fields
with bounds, regex, closed envelope, and the breakdown escape hatch.
Asymmetry is honest about what each surface is.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nt ships

Per diff feedback: the canonical unit for a metric lives at the vendor's
measurement-agent metric definition (brand.json agents[type='measurement']).
The inline `unit` field on every value row is convenience today but
becomes redundant once the measurement-agent surface formalizes per-metric
unit declarations. Schema description now flags the disposition explicitly
and notes the field MAY be deprecated when the agent surface lands.

No behavior change — `unit` was already optional.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…esponsibility

Three minor fixes from the final expert pass on PR #3492:

- Add `x-entity: vendor_metric` to vendor-metric-id.json for consistency
  with brand-id.json:8 (`x-entity: advertiser_brand`). Standard ID-schema
  annotation that was missing.

- Soften the `unit` field description in vendor-metric-value.json. The
  measurement-agent surface that will canonically declare per-metric
  units doesn't ship in 4.0 (early 2027 per release-cadence policy).
  Dropping the "MAY be deprecated" hint avoids signaling instability to
  SSP implementers building dashboards today; descriptive forward-
  reference to brand.json discovery is preserved.

- Clarify dedup responsibility on the (vendor.domain, vendor.brand_id,
  metric_id) semantic uniqueness key. Sellers MUST de-duplicate before
  emission; buyers MAY treat duplicate rows as a seller-side conformance
  bug. Closes the ambiguity about which side enforces.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adding `x-entity: vendor_metric` to vendor-metric-id.json triggered the
context-entity lint because the value wasn't in the registry. Add it to
both the enum and the definitions map at
static/schemas/source/core/x-entity-types.json, and to the editorial
groupings in docs/contributing/x-entity-annotation.md alongside the
existing `vendor_pricing_option` under Vendor services.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley merged commit 1431b6e into main Apr 29, 2026
18 checks passed
@bokelley bokelley deleted the bokelley/vendor-metric-extensions branch April 29, 2026 10:27
@bokelley bokelley added this to the 3.1.0 milestone Apr 29, 2026
bokelley added a commit that referenced this pull request Apr 29, 2026
…ts_sold, new_to_brand_units) (#3507)

* feat(schemas): five missing metric scalars (CPCV, CPM, downloads, units_sold, new_to_brand_units)

Adds five scalars production reporting carries today but had no enum entry:

- cost_per_completed_view — CTV CPCV pricing scalar
- cost_per_thousand — CPM (universal pricing scalar across CTV/display/
  audio/DOOH; conspicuous absence next to cost_per_click before this PR)
- downloads — IAB-standard scalar for audio/podcast (Podcast Measurement
  Technical Guidelines 2.x methodology); distinct from views
- units_sold — Retail-media commerce scalar; distinct from conversions
  (one transaction may carry multiple units)
- new_to_brand_units — Retail-media count of units sold to first-time
  buyers; parallel to existing new_to_brand_rate

All five added as type:number minimum:0 properties on delivery-metrics.json
plus enum entries on available-metric.json. Doc list in
optimization-reporting.mdx updated.

Closes the missing-scalars sub-item of #3460. Other sub-items already
resolved by recent merges:
- Closed-vs-open enum: resolved by #3492 (vendor-metric extensions)
- completion_rate derived ratio: resolved by drop-carve-out call in #3472
DBCFM cross-check remains as a human follow-up (David Porzelt).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(schemas): cost_per_thousand → cpm, drop brand names, tighten descriptions

Three review fixes per expert feedback on #3507:

1. Rename cost_per_thousand → cpm. Both protocol and product expert
   independently flagged that pricing-model.json:8 already uses the
   canonical "cpm" token; calling the scalar cost_per_thousand creates a
   translation mismatch. Operators say "CPM"; OpenRTB, GAM (eCPM),
   TTD all use cpm. Now buyers cross-walk pricing_model→reported scalar
   without a lookup table.

2. Remove real brand names from schema descriptions. delivery-metrics.json
   embedded a real list (Amazon Ads, Walmart Connect, Roundel, Sam's MAP,
   Instacart Ads, Target Roundel) that ships in the published spec —
   playbook policy bars real brand names in schemas. Replaced with
   neutral phrasing. (Bonus: Roundel IS Target's network, so the dup was
   confused.) Same for downloads (Megaphone/Art19/Spotify/Apple Podcasts
   list scrubbed).

3. Tighten descriptions:
   - cpm: fix operator-precedence prose to (spend / impressions) × 1000;
     expand channel list to include mobile/web video and native.
   - units_sold: note attribution windows are platform-specific; sellers
     SHOULD declare via measurement_windows or measurement_terms rather
     than encoding in the scalar name (no _14d/_30d sprawl).
   - new_to_brand_rate (existing field): clarify it's a fraction of
     conversions/transactions, distinguishing it from the new
     new_to_brand_units count.
   - new_to_brand_units: cross-reference the rate field for clarity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant