Skip to content

fix(prebid-analytics): populate bid cpm/status and track timeout bids correctly#302

Merged
jrosendahl-opt merged 6 commits into
masterfrom
fix/prebid-analytics-bid-cpm-status
Jul 16, 2026
Merged

fix(prebid-analytics): populate bid cpm/status and track timeout bids correctly#302
jrosendahl-opt merged 6 commits into
masterfrom
fix/prebid-analytics-bid-cpm-status

Conversation

@jrosendahl-opt

@jrosendahl-opt jrosendahl-opt commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Missing bid CPMs: toWitness was only merging splitTestAssignment from bidsReceivedcpm, size, currency, and status were never populated on individual bids. The downstream Spark job (prebid_analytics_fact.go) uses bid.cpm and bid.status != REQUESTED for its win_cpm fallback (Case 2: MAX(bid_cpm) + 0.01), so this caused win_cpm to silently fall to 0.0 whenever no bidWon event was received.
  • Broken timeout tracking: timeoutBids was destructured from the auctionEnd event where it doesn't exist in Prebid.js — it always defaulted to []. Prebid fires BID_TIMEOUT as a separate event before auctionEnd. Added a pendingTimeoutBids map that accumulates these via a new bidTimeout listener, stores them on AuctionItem, and cleans up after trackAuctionEnd.
  • Bidder request status: NO_BID and TIMEOUT statuses on the bidder request objects in the witness payload are now set correctly, fixing response_count in fact_bid.
    -Handle Page Unload: flush cache when page unloads

payload["bidWonAt"] = null;
payload["optableLoaded"] = !auction.missed;

navigator.sendBeacon(

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.

I think this would still miss. This is async so the unload might tear everything down. I think this needs to run Synchronous

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

My understanding is that this is the purpose of sendBeacon or fetch{keepalive: true}, it will still fire even if you close the tab.

Comment thread lib/addons/prebid/analytics.ts Outdated
Comment thread lib/addons/prebid/analytics.ts Outdated

await disabledAnalytics.trackAuctionEnd(event);
window.dispatchEvent(new Event("beforeunload"));
await new Promise((resolve) => setTimeout(resolve, 10));

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.

We don't nescesarily have the 10ms on browser though

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

What do you suggest instead?

jrosendahl-opt and others added 5 commits July 16, 2026 14:20
… correctly

- Merge cpm, size, currency, and status from bidsReceived into each bid
  in toWitness, fixing the win_cpm fallback in the downstream Spark job
  which requires bid_status != REQUESTED and bid_cpm IS NOT NULL
- Mark bidder request status as NO_BID or TIMEOUT from the respective sets
- Track BID_TIMEOUT events via a new pendingTimeoutBids map (bidTimeout
  fires as a separate Prebid event before auctionEnd, not as a field on it)
- Store timeoutBids on AuctionItem and clean up pendingTimeoutBids after
  trackAuctionEnd; replay missed bidTimeout events in setHooks
- Add tests asserting the actual toWitness payload for cpm, size, currency,
  RECEIVED/NO_BID/TIMEOUT statuses, live bidTimeout listener, and missed
  event replay

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…eunload

Auctions waiting for a bidWon event (held in a setTimeout up to bidWinTimeout)
were silently dropped on page unload. Added a beforeunload listener that cancels
pending timeouts and sends each unresolved auction immediately via
navigator.sendBeacon, which is guaranteed to complete even as the page tears down.
Sampling and analytics-enabled checks are respected. Includes tests for the flush
path, the disabled/already-sent no-op paths, and global listener cleanup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…h via beacon

The `sampled` boolean is now evaluated exactly once in `trackAuctionEnd`
(analytics enabled AND shouldSample()) and stored on `AuctionItem`.
The `handleBeforeUnload` beacon path, the delayed timeout callback, and
`trackBidWon` all read `auction.sampled` instead of re-evaluating, so
auctions in the sampling holdout can never be accidentally sent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
visibilitychange fires reliably on mobile (where beforeunload often
doesn't), and also catches tab switches and app backgrounding. The
handler checks document.visibilityState === 'hidden' before flushing
and deletes each flushed auction from the map to prevent a double-send
if the user returns to the tab and a bidWon fires.

Added a test for the 'visible' (no-op) case alongside the existing
hidden/disabled/holdout/no-pending-timeout cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Integrates origin/master restructuring (bidWonEvents accumulator,
missedAuctionIds tracking, auctionInit replay, EID deduplication,
originSlug field) while preserving our branch additions: bidTimeout
tracking via pendingTimeoutBids, once-per-auction sampled flag, and
visibilitychange beacon flush instead of beforeunload.

Also fixes handleVisibilityChange to pass auction.bidWonEvents to
toWitness and compute bidWonAt from them rather than hardcoding null.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@jrosendahl-opt
jrosendahl-opt force-pushed the fix/prebid-analytics-bid-cpm-status branch from 42f3cd5 to 793a673 Compare July 16, 2026 20:45
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@jrosendahl-opt
jrosendahl-opt merged commit 588f0bf into master Jul 16, 2026
7 checks passed
@jrosendahl-opt
jrosendahl-opt deleted the fix/prebid-analytics-bid-cpm-status branch July 16, 2026 20:55
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.

3 participants