From 1bba6ef267ce40c0ed29d888aad091b573635f20 Mon Sep 17 00:00:00 2001 From: Dan Bunker Date: Fri, 12 Jun 2026 16:24:26 -0600 Subject: [PATCH] PLAT-287894: Add uncapped paid-media calculated-rate fields; deprecate capped originals The maximum:1.0 cap on several calculated-rate fields rejects legitimate values >1.0. Rates whose numerator and denominator come from different event populations can exceed 1.0 (e.g. Google Ads all-conversions/clicks, multiple engagements per impression, spend overrunning budget). These fields are now stable, so the cap cannot be removed or relaxed in place (breaking change, and blocked by the xed-validation restricted-property gate). Instead, add new uncapped sibling fields (minimum:0, no maximum) and mark the capped originals meta:status:deprecated with a pointer to the replacement: ctr -> ctrUncapped conversionRate -> conversionRateUncapped engagementRate -> engagementRateUncapped budgetUtilization -> budgetUtilizationUncapped dailyBudgetUtilization -> dailyBudgetUtilizationUncapped negativeActionRate -> negativeActionRateUncapped positiveActionRate -> positiveActionRateUncapped crossDeviceConversionRate -> crossDeviceConversionRateUncapped Non-breaking: purely additive. Capped fields retain minimum:0, maximum:1 untouched. Examples updated to exercise the new fields (with >1.0 values where the metric can legitimately exceed 1.0). Validation: npm test 2406 passing; lint clean; incompatibility-check clean; xed-validation "No disallowed changes detected" + "All good". Co-Authored-By: Claude Opus 4.8 (1M context) --- ...d-media-attribution-metrics.example.1.json | 1 + ...paid-media-attribution-metrics.schema.json | 9 +++++++++ ...id-media-conversion-metrics.example.1.json | 1 + ...-paid-media-conversion-metrics.schema.json | 10 +++++++++- ...ore-paid-media-cost-metrics.example.1.json | 2 ++ .../core-paid-media-cost-metrics.schema.json | 18 +++++++++++++++++ .../core-paid-media-metrics.example.1.json | 2 ++ .../core-paid-media-metrics.schema.json | 20 +++++++++++++++++-- ...-paid-media-quality-metrics.example.1.json | 2 ++ ...ore-paid-media-quality-metrics.schema.json | 20 +++++++++++++++++-- 10 files changed, 80 insertions(+), 5 deletions(-) diff --git a/components/fieldgroups/paid-media/core-paid-media-attribution-metrics.example.1.json b/components/fieldgroups/paid-media/core-paid-media-attribution-metrics.example.1.json index 2db896467f..86cf05ef5b 100644 --- a/components/fieldgroups/paid-media/core-paid-media-attribution-metrics.example.1.json +++ b/components/fieldgroups/paid-media/core-paid-media-attribution-metrics.example.1.json @@ -9,6 +9,7 @@ "xdm:enabled": true, "xdm:crossDeviceConversions": 425, "xdm:crossDeviceConversionRate": 0.34, + "xdm:crossDeviceConversionRateUncapped": 1.27, "xdm:deviceSwitchRate": 0.42, "xdm:averageDevicesPerPath": 1.8, "xdm:devicePathBreakdown": { diff --git a/components/fieldgroups/paid-media/core-paid-media-attribution-metrics.schema.json b/components/fieldgroups/paid-media/core-paid-media-attribution-metrics.schema.json index faeb0870eb..b7d7a54b08 100644 --- a/components/fieldgroups/paid-media/core-paid-media-attribution-metrics.schema.json +++ b/components/fieldgroups/paid-media/core-paid-media-attribution-metrics.schema.json @@ -133,9 +133,18 @@ "xdm:crossDeviceConversionRate": { "type": "number", "title": "Cross Device Conversion Rate", + "description": "(DEPRECATED — use xdm:crossDeviceConversionRateUncapped instead. Capped at 1.0 and cannot represent rates >1.0 where conversions outnumber the interactions used as the denominator.) Cross-device conversion rate.", + "meta:status": "deprecated", "minimum": 0, "maximum": 1 }, + "xdm:crossDeviceConversionRateUncapped": { + "type": "number", + "title": "Cross Device Conversion Rate (Uncapped)", + "description": "Cross-device conversion rate. Can exceed 1.0 because conversions can outnumber the interactions used as the denominator.", + "meta:status": "stable", + "minimum": 0 + }, "xdm:deviceSwitchRate": { "type": "number", "title": "Device Switch Rate", diff --git a/components/fieldgroups/paid-media/core-paid-media-conversion-metrics.example.1.json b/components/fieldgroups/paid-media/core-paid-media-conversion-metrics.example.1.json index 70fa0cb928..ddf5d48bc1 100644 --- a/components/fieldgroups/paid-media/core-paid-media-conversion-metrics.example.1.json +++ b/components/fieldgroups/paid-media/core-paid-media-conversion-metrics.example.1.json @@ -19,6 +19,7 @@ "xdm:landingPageView": 2200 }, "xdm:conversionRate": 0.0352, + "xdm:conversionRateUncapped": 1.18, "xdm:costPerAction": 10.23, "xdm:conversionLag": 8.3, "xdm:conversionPath": { diff --git a/components/fieldgroups/paid-media/core-paid-media-conversion-metrics.schema.json b/components/fieldgroups/paid-media/core-paid-media-conversion-metrics.schema.json index 5f461ad132..33163f5e89 100644 --- a/components/fieldgroups/paid-media/core-paid-media-conversion-metrics.schema.json +++ b/components/fieldgroups/paid-media/core-paid-media-conversion-metrics.schema.json @@ -112,10 +112,18 @@ "xdm:conversionRate": { "type": "number", "title": "Conversion Rate", - "description": "Overall conversion rate", + "description": "(DEPRECATED — use xdm:conversionRateUncapped instead. Capped at 1.0 and cannot represent rates >1.0 where all-conversions counts include multiple conversions per click.) Overall conversion rate (conversions/clicks).", + "meta:status": "deprecated", "minimum": 0, "maximum": 1 }, + "xdm:conversionRateUncapped": { + "type": "number", + "title": "Conversion Rate (Uncapped)", + "description": "Overall conversion rate (conversions/clicks). Can exceed 1.0 because all-conversions counts can include multiple conversions per click.", + "meta:status": "stable", + "minimum": 0 + }, "xdm:costPerAction": { "type": "number", "title": "Cost Per Action", diff --git a/components/fieldgroups/paid-media/core-paid-media-cost-metrics.example.1.json b/components/fieldgroups/paid-media/core-paid-media-cost-metrics.example.1.json index 9c01b8238b..c27a167704 100644 --- a/components/fieldgroups/paid-media/core-paid-media-cost-metrics.example.1.json +++ b/components/fieldgroups/paid-media/core-paid-media-cost-metrics.example.1.json @@ -27,9 +27,11 @@ "xdm:budgetSpent": 18000, "xdm:budgetRemaining": 42000, "xdm:budgetUtilization": 0.3, + "xdm:budgetUtilizationUncapped": 1.05, "xdm:dailyBudget": 2000, "xdm:dailySpend": 600, "xdm:dailyBudgetUtilization": 0.3, + "xdm:dailyBudgetUtilizationUncapped": 1.12, "xdm:pacingStatus": "on_track", "xdm:projectedSpend": 60000, "xdm:budgetOverrun": 0, diff --git a/components/fieldgroups/paid-media/core-paid-media-cost-metrics.schema.json b/components/fieldgroups/paid-media/core-paid-media-cost-metrics.schema.json index 2ee53a0fa4..1ae43b03ea 100644 --- a/components/fieldgroups/paid-media/core-paid-media-cost-metrics.schema.json +++ b/components/fieldgroups/paid-media/core-paid-media-cost-metrics.schema.json @@ -216,9 +216,18 @@ "xdm:budgetUtilization": { "type": "number", "title": "Budget Utilization", + "description": "(DEPRECATED — use xdm:budgetUtilizationUncapped instead. Capped at 1.0 and cannot represent over-spend where spend exceeds the allocated budget.) Ratio of budget spent to budget allocated.", + "meta:status": "deprecated", "minimum": 0, "maximum": 1 }, + "xdm:budgetUtilizationUncapped": { + "type": "number", + "title": "Budget Utilization (Uncapped)", + "description": "Ratio of budget spent to budget allocated. Can exceed 1.0 when spend overruns the allocated budget.", + "meta:status": "stable", + "minimum": 0 + }, "xdm:dailyBudget": { "type": "number", "title": "Daily Budget", @@ -232,9 +241,18 @@ "xdm:dailyBudgetUtilization": { "type": "number", "title": "Daily Budget Utilization", + "description": "(DEPRECATED — use xdm:dailyBudgetUtilizationUncapped instead. Capped at 1.0 and cannot represent over-spend where daily spend exceeds the daily budget.) Ratio of daily spend to daily budget.", + "meta:status": "deprecated", "minimum": 0, "maximum": 1 }, + "xdm:dailyBudgetUtilizationUncapped": { + "type": "number", + "title": "Daily Budget Utilization (Uncapped)", + "description": "Ratio of daily spend to daily budget. Can exceed 1.0 when daily spend overruns the daily budget.", + "meta:status": "stable", + "minimum": 0 + }, "xdm:pacingStatus": { "type": "string", "title": "Pacing Status", diff --git a/components/fieldgroups/paid-media/core-paid-media-metrics.example.1.json b/components/fieldgroups/paid-media/core-paid-media-metrics.example.1.json index e8b1b9b545..9e72dd67ae 100644 --- a/components/fieldgroups/paid-media/core-paid-media-metrics.example.1.json +++ b/components/fieldgroups/paid-media/core-paid-media-metrics.example.1.json @@ -7,6 +7,7 @@ "xdm:reach": 26000, "xdm:frequency": 1.92, "xdm:ctr": 0.05, + "xdm:ctrUncapped": 0.05, "xdm:cpm": 36.0, "xdm:cpc": 7.2, "xdm:conversions": 1250, @@ -17,6 +18,7 @@ "xdm:videoCompletions": 5785, "xdm:engagements": 12345, "xdm:engagementRate": 0.247, + "xdm:engagementRateUncapped": 1.34, "xdm:additionalMetrics": [ { "xdm:metricName": "outbound_clicks", diff --git a/components/fieldgroups/paid-media/core-paid-media-metrics.schema.json b/components/fieldgroups/paid-media/core-paid-media-metrics.schema.json index 2ad3ab5d98..11bf4eb88e 100644 --- a/components/fieldgroups/paid-media/core-paid-media-metrics.schema.json +++ b/components/fieldgroups/paid-media/core-paid-media-metrics.schema.json @@ -57,10 +57,18 @@ "xdm:ctr": { "type": "number", "title": "Click-Through Rate", - "description": "Click-through rate (clicks/impressions)", + "description": "(DEPRECATED — use xdm:ctrUncapped instead. Capped at 1.0 and cannot represent rates >1.0 reported by networks that count more tracked clicks than impressions.) Click-through rate (clicks/impressions).", + "meta:status": "deprecated", "minimum": 0, "maximum": 1 }, + "xdm:ctrUncapped": { + "type": "number", + "title": "Click-Through Rate (Uncapped)", + "description": "Click-through rate (clicks/impressions). Can exceed 1.0 because networks may report more tracked clicks than impressions or inflate reported counts.", + "meta:status": "stable", + "minimum": 0 + }, "xdm:cpm": { "type": "number", "title": "Cost Per Mille", @@ -118,10 +126,18 @@ "xdm:engagementRate": { "type": "number", "title": "Engagement Rate", - "description": "Engagement rate (engagements/impressions)", + "description": "(DEPRECATED — use xdm:engagementRateUncapped instead. Capped at 1.0 and cannot represent rates >1.0 where a single impression yields multiple engagements.) Engagement rate (engagements/impressions).", + "meta:status": "deprecated", "minimum": 0, "maximum": 1 }, + "xdm:engagementRateUncapped": { + "type": "number", + "title": "Engagement Rate (Uncapped)", + "description": "Engagement rate (engagements/impressions). Can exceed 1.0 because a single impression can yield multiple engagements (e.g. like, share, and comment).", + "meta:status": "stable", + "minimum": 0 + }, "xdm:additionalMetrics": { "title": "Additional Metrics", "description": "Platform-specific or custom metrics not defined in core schema. Allows ingestion of arbitrary metrics from paid media network APIs without requiring schema changes. For conversion attribution window breakdown (Meta Insights API), use metricName like {actionType}_1d_click, {actionType}_7d_click, {actionType}_1d_view (e.g. purchases_1d_click, leads_7d_click) and metricType 'attribution'.", diff --git a/components/fieldgroups/paid-media/core-paid-media-quality-metrics.example.1.json b/components/fieldgroups/paid-media/core-paid-media-quality-metrics.example.1.json index 21508681fe..4049c94cc6 100644 --- a/components/fieldgroups/paid-media/core-paid-media-quality-metrics.example.1.json +++ b/components/fieldgroups/paid-media/core-paid-media-quality-metrics.example.1.json @@ -7,8 +7,10 @@ "xdm:overallQualityScore": 8.67, "xdm:negativeActions": 25, "xdm:negativeActionRate": 0.0005, + "xdm:negativeActionRateUncapped": 0.0005, "xdm:positiveActions": 12345, "xdm:positiveActionRate": 0.247, + "xdm:positiveActionRateUncapped": 1.41, "xdm:brandSafetyScore": 9.5, "xdm:viewabilityScore": 8.5, "xdm:fraudScore": 0.5, diff --git a/components/fieldgroups/paid-media/core-paid-media-quality-metrics.schema.json b/components/fieldgroups/paid-media/core-paid-media-quality-metrics.schema.json index d97a3ff530..9d44c75d32 100644 --- a/components/fieldgroups/paid-media/core-paid-media-quality-metrics.schema.json +++ b/components/fieldgroups/paid-media/core-paid-media-quality-metrics.schema.json @@ -61,10 +61,18 @@ "xdm:negativeActionRate": { "type": "number", "title": "Negative Action Rate", - "description": "Rate of negative actions", + "description": "(DEPRECATED — use xdm:negativeActionRateUncapped instead. Capped at 1.0 and cannot represent rates >1.0 where a single impression yields multiple negative actions.) Rate of negative actions (negative actions/impressions).", + "meta:status": "deprecated", "minimum": 0, "maximum": 1 }, + "xdm:negativeActionRateUncapped": { + "type": "number", + "title": "Negative Action Rate (Uncapped)", + "description": "Rate of negative actions (negative actions/impressions). Can exceed 1.0 because a single impression can yield multiple negative actions.", + "meta:status": "stable", + "minimum": 0 + }, "xdm:positiveActions": { "type": "integer", "title": "Positive Actions", @@ -74,10 +82,18 @@ "xdm:positiveActionRate": { "type": "number", "title": "Positive Action Rate", - "description": "Rate of positive actions", + "description": "(DEPRECATED — use xdm:positiveActionRateUncapped instead. Capped at 1.0 and cannot represent rates >1.0 where a single impression yields multiple positive actions.) Rate of positive actions (positive actions/impressions).", + "meta:status": "deprecated", "minimum": 0, "maximum": 1 }, + "xdm:positiveActionRateUncapped": { + "type": "number", + "title": "Positive Action Rate (Uncapped)", + "description": "Rate of positive actions (positive actions/impressions). Can exceed 1.0 because a single impression can yield multiple positive actions.", + "meta:status": "stable", + "minimum": 0 + }, "xdm:brandSafetyScore": { "type": "number", "title": "Brand Safety Score",