Skip to content

analytics: per-bidder EID delivery and troubleshooting fields in auction_processed - #351

Open
MO-Thibault wants to merge 2 commits into
Optable:masterfrom
MO-Thibault:mo/analytics-capture-fields
Open

MO-Thibault wants to merge 2 commits into
Optable:masterfrom
MO-Thibault:mo/analytics-capture-fields

Conversation

@MO-Thibault

@MO-Thibault MO-Thibault commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds the fields we kept missing during enrichment and uplift investigations to the V1 analytics add-on (lib/addons/prototypes/analytics.js), which feeds bid_analytics. Each one maps to a case that needed a browser harness or a partner conversation because the table could not answer it.

Per-bidder EID delivery, with no payload growth in the normal case. Today each bidder request's optableSources is computed and then collapsed into one auction-level union, so we cannot tell whether a given bidder received our IDs (ddm: whether msft got any after the appnexus rename). Each bidder request now carries om, a bitmask over the auction-level optableSources (bit i = optableSources[i]). It is omitted when the bidder received every source, which is the common case, so most payloads do not change. Absent means all sources; 0 means none.

New fields

Field Level Size Meaning
bidderRequests[].om bidder 0 bytes usually, ~7 when present Sources this bidder received, as a bitmask over optableSources
bidderRequests[].bids[].src bid only on s2s bids "s2s" for Prebid Server responses
unmatchedBids auction only when > 0 Received bids that matched no bidder request
slotSeq auction ~10 bytes Earlier auctions of this ad unit on this page view (0 = first, >0 = refresh or re-auction)
t0 auction ~10 bytes ms from page start to auction start
consent auction ~12 bytes Bitmask: 1 GDPR applies, 2 GPC, 4 US privacy opt-out, 8 GPP present, 16 TCF blocked storage
cacheAge auction ~15 bytes Seconds since the targeting cache was written; -1 when none
idsMs auction only when written this page ms from page start to our IDs landing (compare with t0)
page first auction of a page only ~90 bytes once { optableRTDInstalled, optableRTDConfigured, rtdDelay, udDelay }

Documented in lib/addons/prototypes/README.md.

RTD presence in page

  • optableRTDInstalled (1/0): optableRtdProvider is in pbjs.installedModules, meaning it is compiled into the Prebid build.
  • optableRTDConfigured (1/0): realTimeData.dataProviders contains a provider named optable. Renamed from rtdOptable.

Together they separate "module missing from the build" from "built in but not configured". Paired with the per-bidder om field, they also separate "configured but delivered no EIDs".

V2 add-on

The page object is also ported to the V2 add-on (lib/addons/prebid/analytics.ts), with the same shape, in the optable.prebid.auction payload. V2 builds payloads after bidWinTimeout, when later auctions may already have bumped pageAuctionsCount. So it sends page on the first payload through an instance flag rather than pageAuctionsCount === 1. When Prebid exposes no getConfig, page is omitted and retried on the next payload. Documented in lib/addons/prebid/analytics.md.

Prebid Server responses

On ddm, prebidServer requests show about 6.7M per day with 0 priced bids recorded. The likely cause is that s2s bid responses carry a requestId that matches no client bidId, so trackAuctionEnd drops them. Received bids that do not match now fall back to the same bidder code and ad unit (first bid still waiting for a response), keep src: "s2s", and anything still unmatched is counted in unmatchedBids. Not yet verified on a live Prebid Server page; unmatchedBids will tell us whether the fallback catches them.

Core change

LocalStorage.setTargeting now also writes <cacheKey>:ts with the write time, and clearTargeting removes it. The cache value itself is unchanged, so wrappers that parse OPTABLE_RESOLVED are unaffected. Control-group clearing that removes OPTABLE_RESOLVED directly leaves the :ts key behind, which is harmless: analytics reports an age only when the cache itself is present.

Not in this PR

  • The bid_analytics ETL still needs columns for these fields (separate change in optable-solutions). Until then they are only in the raw payload.
  • In the V2 add-on (lib/addons/prebid/analytics.ts), only page is added. The other fields remain V1-only.
  • Bundles need a web-sdk bump to pick this up.

Test plan

  • New lib/addons/prototypes/analytics.test.js (16 tests): om omitted and masked, slotSeq counting, t0, each consent bit, the three cache states and custom key, page config on the first auction only, RTD installed vs configured, s2s fallback match, unmatched count, client bids unchanged.
  • lib/addons/prebid/analytics.test.ts (4 new tests): V2 page on the first payload only, RTD installed vs configured, and the missing installedModules / getConfig cases.
  • lib/core/storage.test.js: :ts written on setTargeting, removed on clearTargeting.
  • Full suite: 30 suites, 506 tests pass. pnpm build-lib passes. pnpm format leaves no diff.
  • Load a bundle built from this branch on a test page and check one auction_processed payload.
  • On a Prebid Server tenant (ddm), confirm s2s bids now carry CPMs and check unmatchedBids.

🤖 Generated with Claude Code

…ion_processed

Per-bidder delivery as a bitmask over the auction's optableSources, omitted
when a bidder received every source. Adds slotSeq, t0, consent flags, cache
age and idsMs, first-auction page config (RTD registration and both
auctionDelay values), and Prebid Server bid matching with an unmatched count.
Storage writes a <cacheKey>:ts sibling so cache age can be measured.

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

Copy link
Copy Markdown
Contributor

Should we add this to V2 (prebid/analytics.ts) too ?

Rename page.rtdOptable to page.optableRTDConfigured and add
page.optableRTDInstalled (optableRtdProvider in pbjs.installedModules),
so a module missing from the Prebid build can be told apart from one
built in but not configured.

Port the page object to the V2 add-on. V2 builds payloads after
bidWinTimeout, so it sends page on the first payload via an instance
flag rather than pageAuctionsCount === 1.
@jplaroche
jplaroche marked this pull request as ready for review October 16, 2026 00:15
@jplaroche
jplaroche requested review from a team as code owners October 16, 2026 00:15

@jplaroche jplaroche left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

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.

2 participants