1(function () { 2 'use strict'; 3 4 var STORAGE_KEY = 'pm_bid_stats'; 5 var USER_ID_KEY = 'pm_user_id'; 6 var STORAGE_VERSION = 4; 7 var LEARNING_THRESHOLD = 10; 8 var TIER1_MAX = 10; 9 var DATA_TTL_DAYS = 7; 10 var MAX_RECENT_CPMS = 30; 11 var FLOOR_MAX = 2.00; 12 var EMPTY_STREAK_TIER2 = 4; 13 var EXPLORE_EVERY_N = 5; 14 15 // ââ Anti-gaming: jitter range for floor adjustments ââ 16 // Each win/loss applies a random bump in [JITTER_MIN, JITTER_MAX] so DSPs 17 // cannot reverse-engineer a fixed formula. 18 var JITTER_MIN = 0.005; // 0.5% 19 var JITTER_MAX = 0.015; // 1.5% 20 21 // ââ Anti-gaming: batch floor updates every 12â24 hours ââ 22 // Floors do not change immediately after an auction; they update once the 23 // batch window expires. The window is randomized to break the cause-and- 24 // effect link between individual auctions and floor moves. 25 var BATCH_INTERVAL_MIN = 12 * 3600 * 1000; // 12 h in ms 26 var BATCH_INTERVAL_MAX = 24 * 3600 * 1000; // 24 h in ms 27 28 // ââ Anti-gaming: cap floor at 80â85 % of average eCPM ââ 29 // Never squeeze the floor all the way to 100 % so DSPs still feel a margin. 30 var FLOOR_CAP_MIN = 0.70; 31 var FLOOR_CAP_MAX = 0.75; 32 33 // ââ Control group: 10 % of users always receive a static country-minimum floor ââ 34 // Stored permanently in localStorage so the same user stays in the same group. 35 var CONTROL_GROUP_THRESHOLD = 0.10; 36 37 // Country-based hard minimum floors (EUR CPM) 38 // Set window.userCountry = 'US' / 'CA' / 'GB' etc. server-side before this script loads 39 var TIER1_COUNTRIES = ['GB', 'AU', 'DE', 'FR', 'NL', 'SE', 'NO', 'DK', 'FI', 'CH', 'AT', 'IE', 'NZ', 'JP', 'SG']; 40 41 function getCountryFloor() { 42 var country = (window.userCountry || '').toUpperCase(); 43 if (country === 'US') return 0.20; 44 if (country === 'CA') return 0.15; 45 if (TIER1_COUNTRIES.indexOf(country) >= 0) return 0.10; 46 return 0.04; 47 } 48 49 var allBidderDefs = []; 50 var adSlots = []; 51 var auctionCount = 0; 52 var lastAuctionContext = null; 53 54 function getUserId() { 55 try { 56 var id = localStorage.getItem(USER_ID_KEY); 57 if (!id) { 58 id = 'u_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 10); 59 localStorage.setItem(USER_ID_KEY, id); 60 } 61 return id; 62 } catch (e) { return 'unknown'; } 63 } 64 65 var LOGGING_ENABLED = false; 66 67 function logAuction(data) { 68 if (!LOGGING_ENABLED) return; 69 try { 70 fetch('https://auction-logger.solitairen-contacts.workers.dev/log?key=Lala33*', { 71 method: 'POST', 72 headers: { 'Content-Type': 'application/json' }, 73 body: JSON.stringify(data) 74 }); 75 } catch (e) { } 76 } 77 78 function freshStats() { 79 return { 80 version: STORAGE_VERSION, 81 bidders: {}, 82 adUnits: {}, 83 totalWins: 0, 84 // Assigned once per browser; value in [0, 1). 85 // Users below CONTROL_GROUP_THRESHOLD get a static floor forever. 86 userGroup: Math.random(), 87 updatedAt: Date.now() 88 }; 89 } 90 91 // Always returns a valid stats object, never null 92 function load() { 93 try { 94 var raw = localStorage.getItem(STORAGE_KEY); 95 if (raw) { 96 var parsed = JSON.parse(raw); 97 if (parsed && parsed.version === STORAGE_VERSION && typeof parsed.totalWins === 'number') { 98 // Back-fill userGroup for sessions created before this version 99 if (typeof parsed.userGroup !== 'number') { 100 parsed.userGroup = Math.random(); 101 } 102 pruneStaleData(parsed); 103 return parsed; 104 } 105 } 106 } catch (e) { } 107 return freshStats(); 108 } 109 110 // THE stats object â guaranteed non-null 111 var stats = freshStats(); 112 try { stats = load(); } catch (e) { } 113 114 function getStats() { 115 if (!stats) stats = freshStats(); 116 return stats; 117 } 118 119 function save() { 120 try { 121 var s = getStats(); 122 s.updatedAt = Date.now(); 123 localStorage.setItem(STORAGE_KEY, JSON.stringify(s)); 124 } catch (e) { } 125 } 126 127 function pruneStaleData(s) { 128 var cutoff = Date.now() - DATA_TTL_DAYS * 86400000;
129 Object.keys(s.bidders).forEach(function (b) { 130 if (s.bidders[b].lastSeen < cutoff) delete s.bidders[b]; 131 }); 132 } 133 134 function scoreBidder(b) { 135 var winRate = b.wins / Math.max(b.bids, 1); 136 var avgCpm = b.wins > 0 ? b.totalCpm / b.wins : 0; 137 return winRate * avgCpm; 138 } 139 140 function classifyBidders() { 141 var s = getStats(); 142 var scored = Object.entries(s.bidders) 143 .filter(function (e) { return e[1].bids > 0; }) 144 .map(function (e) { return { name: e[0], score: scoreBidder(e[1]) }; }) 145 .sort(function (a, b) { return b.score - a.score; }); 146 147 var tier1Names = scored.slice(0, TIER1_MAX).map(function (x) { return x.name; }); 148 var tier2Names = scored.slice(TIER1_MAX).map(function (x) { return x.name; }); 149 150 var knownNames = {}; 151 scored.forEach(function (x) { knownNames[x.name] = true; }); 152 allBidderDefs.forEach(function (def) { 153 if (!knownNames[def[0]]) tier2Names.push(def[0]); 154 }); 155 156 return { tier1: tier1Names, tier2: tier2Names }; 157 } 158 159 function getBidsForNames(names) { 160 var nameSet = {}; 161 names.forEach(function (n) { nameSet[n] = true; }); 162 return allBidderDefs 163 .filter(function (def) { return nameSet[def[0]]; }) 164 .map(function (def) { return { bidder: def[0], params: def[1] }; }); 165 } 166 167 function randomBatchInterval() { 168 return BATCH_INTERVAL_MIN + Math.random() * (BATCH_INTERVAL_MAX - BATCH_INTERVAL_MIN); 169 } 170 171 function ensureAdUnit(divId) { 172 var s = getStats(); 173 if (!s.adUnits[divId]) { 174 s.adUnits[divId] = { 175 consecutiveEmpty: 0, 176 recentWinCpms: [], 177 auctionsSinceTier1: 0, 178 currentFloor: null, // null = uninitialized 179 pendingUpdates: [], // buffered win/loss events awaiting batch flush 180 lastFloorUpdate: 0, // timestamp of last batch flush 181 batchInterval: randomBatchInterval() 182 }; 183 } 184 var unit = s.adUnits[divId]; 185 // Back-fill new fields for units saved before this version 186 if (!Array.isArray(unit.pendingUpdates)) unit.pendingUpdates = []; 187 if (typeof unit.lastFloorUpdate !== 'number') unit.lastFloorUpdate = 0; 188 if (typeof unit.batchInterval !== 'number') unit.batchInterval = randomBatchInterval(); 189 return unit; 190 } 191 192 // Returns average of stored win CPMs, or 0 if none 193 function avgCpm(unit) { 194 var cpms = unit.recentWinCpms; 195 if (!cpms || cpms.length === 0) return 0; 196 return cpms.reduce(function (a, b) { return a + b; }, 0) / cpms.length; 197 } 198 199 // Returns a jitter multiplier in [JITTER_MIN, JITTER_MAX] 200 function jitter() { 201 return JITTER_MIN + Math.random() * (JITTER_MAX - JITTER_MIN); 202 } 203 204 // Apply all buffered win/loss events to currentFloor and reset the queue. 205 // Uses jittered adjustments and caps at 80â85 % of avgCPM. 206 function flushPendingUpdates(unit) { 207 var countryMin = getCountryFloor(); 208 var floor = unit.currentFloor; 209 210 if (floor === null) { 211 var avg = avgCpm(unit); 212 floor = avg > 0 ? avg * 0.70 : countryMin; 213 } 214 215 unit.pendingUpdates.forEach(function (update) { 216 var bump = jitter(); 217 if (update.won) { 218 floor *= (1 + bump); 219 } else { 220 floor *= (1 - bump); 221 } 222 }); 223 224 // Cap: never let the floor exceed 80â85 % of the user's average eCPM 225 var avg = avgCpm(unit); 226 if (avg > 0) { 227 var capFactor = FLOOR_CAP_MIN + Math.random() * (FLOOR_CAP_MAX - FLOOR_CAP_MIN); 228 floor = Math.min(floor, avg * capFactor); 229 } 230 231 unit.currentFloor = Math.max(countryMin, Math.min(FLOOR_MAX, parseFloat(floor.toFixed(4)))); 232 unit.pendingUpdates = []; 233 unit.lastFloorUpdate = Date.now(); 234 unit.batchInterval = randomBatchInterval(); // re-randomize next window 235 } 236 237 // Queue a win/loss event â does NOT touch currentFloor immediately. 238 // The floor only changes when the batch window expires (see computeFloor). 239 function updateFloor(divId, won) { 240 var unit = ensureAdUnit(divId); 241 unit.pendingUpdates.push({ won: won, ts: Date.now() }); 242 } 243 244 // Returns the active floor to enforce for a given slot. 245 // Flushes the pending queue if the batch window has expired. 246 function computeFloor(divId, isTier2) { 247 var countryMin = getCountryFloor(); 248 var s = getStats(); 249 250 // ââ Control group (10 %): always use a flat, static country-minimum floor ââ 251 if (s.userGroup < CONTROL_GROUP_THRESHOLD) { 252 return countryMin; 253 } 254 255 // Tier 2 is already the fallback â use country minimum to maximise fill 256 if (isTier2) return countryMin; 257 258 var unit = ensureAdUnit(divId); 259 260 // Flush pending updates if the batch window has elapsed 261 if (unit.pendingUpdates.length > 0 && 262 Date.now() - unit.lastFloorUpdate >= unit.batchInterval) { 263 flushPendingUpdates(unit); 264 save(); 265 } 266 267 if (unit.currentFloor === null) { 268 // No flush has happened yet: compute an initial floor from avg eCPM 269 var avg = avgCpm(unit); 270 var floor = avg > 0 ? avg * 0.70 : countryMin; 271 // Still respect the cap even before the first flush 272 if (avg > 0) { 273 var capFactor = FLOOR_CAP_MIN + Math.random() * (FLOOR_CAP_MAX - FLOOR_CAP_MIN); 274 floor = Math.min(floor, avg * capFactor); 275 } 276 return Math.max(countryMin, Math.min(FLOOR_MAX, parseFloat(floor.toFixed(4)))); 277 } 278 return unit.currentFloor; 279 } 280 281 function buildFloorData(isTier2, slots) { 282 var values = {};
283 slots.forEach(function (slot) { 284 values[slot.slotName + '|banner|*'] = computeFloor(slot.divId, isTier2); 285 }); 286 return { 287 currency: 'EUR', 288 skipRate: 0, 289 modelVersion: 'dynamic-v3', 290 schema: { fields: ['gptSlot', 'mediaType', 'size'] }, 291 values: values 292 }; 293 } 294 295 function recordAuctionResults(bidResponses, slots) { 296 var s = getStats(); 297 var now = Date.now(); 298 var anyWin = false; 299 300 if (!bidResponses || typeof bidResponses !== 'object') { 301 slots.forEach(function (slot) { 302 var unit = ensureAdUnit(slot.divId); 303 unit.consecutiveEmpty++; 304 updateFloor(slot.divId, false); 305 }); 306 save(); 307 return false; 308 } 309 310 var seenUnits = {}; 311 312 Object.keys(bidResponses).forEach(function (adUnitCode) { 313 seenUnits[adUnitCode] = true; 314 var response = bidResponses[adUnitCode]; 315 var unit = ensureAdUnit(adUnitCode); 316 var bidsArr = (response && Array.isArray(response.bids)) ? response.bids : []; 317 318 if (bidsArr.length > 0) { 319 anyWin = true; 320 unit.consecutiveEmpty = 0; 321 unit.auctionsSinceTier1 = 0; 322 323 bidsArr.forEach(function (bid) { 324 var bidderName = bid.bidderCode || bid.bidder || 'unknown'; 325 if (!s.bidders[bidderName]) { 326 s.bidders[bidderName] = { wins: 0, bids: 0, totalCpm: 0, lastSeen: now }; 327 } 328 var b = s.bidders[bidderName]; 329 b.bids++; 330 b.lastSeen = now; 331 }); 332 333 // Floor update for wins is handled by the bidWon event listener 334 } else { 335 unit.consecutiveEmpty++; 336 updateFloor(adUnitCode, false); 337 } 338 }); 339 340 slots.forEach(function (slot) { 341 if (!seenUnits[slot.divId]) { 342 ensureAdUnit(slot.divId).consecutiveEmpty++; 343 updateFloor(slot.divId, false); 344 } 345 }); 346 347 try { 348 if (typeof pbjs !== 'undefined' && typeof pbjs.getNoBids === 'function') { 349 pbjs.getNoBids().forEach(function (noBid) { 350 var bidderName = noBid.bidder || noBid.bidderCode || 'unknown'; 351 if (!s.bidders[bidderName]) { 352 s.bidders[bidderName] = { wins: 0, bids: 0, totalCpm: 0, lastSeen: now }; 353 } 354 s.bidders[bidderName].bids++; 355 s.bidders[bidderName].lastSeen = now; 356 }); 357 } 358 } catch (e) { } 359 360 save(); 361 return anyWin; 362 } 363 364 function decideTier() { 365 var s = getStats(); 366 if (s.totalWins < LEARNING_THRESHOLD) return 'all'; 367 368 var needsFallback = false; 369 for (var i = 0; i < adSlots.length; i++) { 370 if (ensureAdUnit(adSlots[i].divId).consecutiveEmpty >= EMPTY_STREAK_TIER2) { 371 needsFallback = true; 372 break; 373 } 374 } 375 376 if (needsFallback) { 377 auctionCount++; 378 if (auctionCount % EXPLORE_EVERY_N === 0) return 'tier1'; 379 return 'tier2'; 380 } 381 return 'tier1'; 382 } 383 384 // ââ Guaranteed GAM refresh â the nuclear option if everything else fails ââ 385 function forceGAMRefresh() { 386 try { 387 pbjs.setTargetingForGPTAsync(); 388 googletag.cmd.push(function () { googletag.pubads().refresh(); }); 389 } catch (e) { 390 console.error('[BidManager] Even GAM refresh failed:', e); 391 } 392 } 393 394 window.BidManager = { 395 396 init: function (bidderDefs, slots) { 397 allBidderDefs = bidderDefs; 398 adSlots = slots; 399 try { stats = load(); } catch (e) { stats = freshStats(); } 400 401 // Reset streaks on every page load â always start with tier 1 402 var s = getStats();
403 Object.keys(s.adUnits).forEach(function (k) { 404 s.adUnits[k].consecutiveEmpty = 0; 405 }); 406 save(); 407 408 // Track actual wins via prebid event (status flags aren't set at bidsBackHandler time) 409 try { 410 pbjs.onEvent('bidWon', function (bid) { 411 var ws = getStats(); 412 var now = Date.now(); 413 var bidderName = bid.bidderCode || bid.bidder || 'unknown'; 414 if (!ws.bidders[bidderName]) { 415 ws.bidders[bidderName] = { wins: 0, bids: 0, totalCpm: 0, lastSeen: now }; 416 } 417 var b = ws.bidders[bidderName]; 418 b.wins++; 419 b.totalCpm += bid.cpm || 0; 420 b.lastSeen = now; 421 ws.totalWins++; 422 423 var unit = ensureAdUnit(bid.adUnitCode); 424 unit.recentWinCpms.push(bid.cpm || 0); 425 if (unit.recentWinCpms.length > MAX_RECENT_CPMS) unit.recentWinCpms.shift(); 426 updateFloor(bid.adUnitCode, true); // queued â not applied until batch flush 427 save(); 428 429 }); 430 } catch (e) { } 431 432 try { 433 googletag.cmd.push(function () { 434 googletag.pubads().addEventListener('slotRenderEnded', function (event) { 435 if (!lastAuctionContext) return; 436 var slotId = event.slot.getSlotElementId(); 437 var winner = lastAuctionContext.winners[slotId] || null; 438 var source = event.isEmpty ? 'empty' : (winner ? 'ssp' : 'google'); 439 logAuction({ 440 userId: lastAuctionContext.userId, 441 ts: new Date().toISOString(), 442 country: lastAuctionContext.country, 443 group: lastAuctionContext.group, 444 phase: lastAuctionContext.phase, 445 tier: lastAuctionContext.tier, 446 floors: lastAuctionContext.floors, 447 biddersInvited: lastAuctionContext.biddersInvited, 448 biddersResponded: lastAuctionContext.biddersResponded, 449 biddersSilent: lastAuctionContext.biddersSilent, 450 adUnits: lastAuctionContext.adUnits, 451 slotId: slotId, 452 source: source, 453 winner: winner, 454 advertiserId: event.advertiserId || null, 455 lineItemId: event.lineItemId || null, 456 creativeId: event.creativeId || null 457 }); 458 }); 459 }); 460 } catch (e) { } 461 462 var isControl = s.userGroup < CONTROL_GROUP_THRESHOLD; 463 console.log('[BidManager] Loaded: ' + s.totalWins + ' wins, ' + 464 Object.keys(s.bidders).length + ' bidders, ' + 465 'phase: ' + (s.totalWins < LEARNING_THRESHOLD ? 'learning' : 'tiered') + ', ' + 466 'country: ' + (window.userCountry || 'unknown') + ', ' + 467 'floor min: $' + getCountryFloor() + ', ' + 468 'group: ' + (isControl ? 'CONTROL (static floor)' : 'DYNAMIC (group=' + s.userGroup.toFixed(3) + ')')); 469 }, 470 471 runAuction: function (timeout, activeSlots) { 472 // ââ If anything at all goes wrong, fall back to original behavior ââ 473 try { 474 if (typeof pbjs === 'undefined' || typeof googletag === 'undefined') return; 475 476 var slots = activeSlots || adSlots; 477 var tier = decideTier(); 478 var tiers = classifyBidders(); 479 var isTier2 = tier === 'tier2'; 480 481 var bids; 482 if (tier === 'all') { 483 bids = allBidderDefs.map(function (def) { return { bidder: def[0], params: def[1] }; }); 484 } else if (tier === 'tier1') { 485 bids = getBidsForNames(tiers.tier1); 486 } else { 487 bids = getBidsForNames(tiers.tier2); 488 } 489 490 // Always include every bidder whose name contains "server" (case-insensitive), 491 // regardless of tier â add only those not already present in bids. 492 var presentBidders = {}; 493 bids.forEach(function (b) { presentBidders[b.bidder] = true; });
494 allBidderDefs.forEach(function (def) { 495 if (def[0].toLowerCase().indexOf('server') >= 0 && !presentBidders[def[0]]) { 496 bids.push({ bidder: def[0], params: def[1] }); 497 } 498 }); 499 500 try { 501 pbjs.setConfig({ 502 floors: { 503 enforcement: { enforceJS: true, enforcePBS: true, floorDeals: true }, 504 data: buildFloorData(isTier2, slots) 505 } 506 }); 507 } catch (e) { 508 console.error('[BidManager] Floor config error:', e); 509 } 510 511 try { 512 slots.forEach(function (slot) { pbjs.removeAdUnit(slot.divId); }); 513 } catch (e) { } 514 515 pbjs.addAdUnits(slots.map(function (slot) { 516 return { code: slot.divId, mediaTypes: { banner: { sizes: slot.sizes } }, bids: bids }; 517 })); 518 519 var floorSummary = slots.map(function (slot) { 520 return slot.divId + ':$' + computeFloor(slot.divId, isTier2); 521 }).join(', '); 522 console.log('[BidManager] Auction: tier=' + tier + ', bidders=' + bids.length + ', slots=' + slots.length + ', floors=[' + floorSummary + ']'); 523 524 var self = this; 525 pbjs.requestBids({ 526 timeout: timeout, 527 bidsBackHandler: function (bidResponses) { 528 var anyWin = false; 529 try { 530 anyWin = recordAuctionResults(bidResponses, slots); 531 } catch (e) { 532 console.error('[BidManager] Record error:', e); 533 } 534 535 try { 536 var ls = getStats(); 537 var isCtrl = ls.userGroup < CONTROL_GROUP_THRESHOLD; 538 var invitedBidders = bids.map(function (b) { return b.bidder; }); 539 var respondedBidders = []; 540 var silentBidders = []; 541 var adUnitLogs = []; 542 543 Object.keys(bidResponses || {}).forEach(function (code) { 544 var resp = bidResponses[code]; 545 var bidsArr = (resp && Array.isArray(resp.bids)) ? resp.bids : []; 546 var unitBids = bidsArr.map(function (bid) { 547 var name = bid.bidderCode || bid.bidder || 'unknown'; 548 if (respondedBidders.indexOf(name) < 0) respondedBidders.push(name); 549 return { bidder: name, cpm: bid.cpm, currency: bid.currency || 'EUR' }; 550 }); 551 adUnitLogs.push({ adUnit: code, bids: unitBids }); 552 }); 553 554 invitedBidders.forEach(function (name) { 555 if (respondedBidders.indexOf(name) < 0) silentBidders.push(name); 556 }); 557 558 var winners = {}; 559 if (anyWin) { 560 adUnitLogs.forEach(function (u) { 561 var top = null; 562 u.bids.forEach(function (b) { 563 if (!top || b.cpm > top.cpm) top = b; 564 }); 565 if (top) winners[u.adUnit] = { bidder: top.bidder, cpm: top.cpm, currency: top.currency }; 566 }); 567 } 568 569 lastAuctionContext = { 570 userId: getUserId(), 571 country: window.userCountry || 'unknown', 572 group: isCtrl ? 'control' : 'dynamic', 573 phase: ls.totalWins < LEARNING_THRESHOLD ? 'learning' : 'tiered', 574 tier: tier, 575 floors: slots.map(function (slot) { 576 return { divId: slot.divId, floor: computeFloor(slot.divId, isTier2) }; 577 }), 578 biddersInvited: invitedBidders, 579 biddersResponded: respondedBidders, 580 biddersSilent: silentBidders, 581 adUnits: adUnitLogs, 582 anyWin: anyWin, 583 winners: winners 584 }; 585 } catch (e) { } 586 587 if (anyWin) { 588 forceGAMRefresh();
589 } else if (tier === 'tier1' && getStats().totalWins >= LEARNING_THRESHOLD) { 590 console.log('[BidManager] Tier 1 empty â Tier 2'); 591 self.runAuction(timeout, activeSlots); 592 } else { 593 forceGAMRefresh(); 594 } 595 } 596 }); 597 598 } catch (e) { 599 // ââ TOTAL FALLBACK: something crashed, just run GAM directly ââ 600 console.error('[BidManager] Auction crashed, forcing GAM refresh:', e); 601 forceGAMRefresh(); 602 } 603 }, 604 605 getStats: function () { 606 var s = getStats(); 607 var tiers = s.totalWins >= LEARNING_THRESHOLD ? classifyBidders() : null; 608 var countryMin = getCountryFloor(); 609 var isControl = s.userGroup < CONTROL_GROUP_THRESHOLD; 610 return { 611 phase: s.totalWins < LEARNING_THRESHOLD ? 'learning' : 'tiered', 612 totalWins: s.totalWins, 613 country: window.userCountry || 'unknown', 614 countryFloor: countryMin, 615 group: isControl ? 'control' : 'dynamic', 616 userGroup: s.userGroup, 617 bidders: Object.entries(s.bidders) 618 .map(function (e) { 619 return { 620 name: e[0], wins: e[1].wins, bids: e[1].bids, 621 avgCpm: e[1].wins > 0 ? (e[1].totalCpm / e[1].wins).toFixed(4) : '0', 622 score: scoreBidder(e[1]).toFixed(4), 623 tier: tiers ? (tiers.tier1.indexOf(e[0]) >= 0 ? 1 : 2) : '-' 624 }; 625 }) 626 .sort(function (a, b) { return parseFloat(b.score) - parseFloat(a.score); }), 627 adUnits: Object.entries(s.adUnits).map(function (e) { 628 var unit = e[1]; 629 var pendingCount = Array.isArray(unit.pendingUpdates) ? unit.pendingUpdates.length : 0; 630 var msUntilFlush = unit.lastFloorUpdate > 0 631 ? Math.max(0, (unit.lastFloorUpdate + (unit.batchInterval || BATCH_INTERVAL_MIN)) - Date.now()) 632 : 0; 633 return { 634 divId: e[0], 635 consecutiveEmpty: unit.consecutiveEmpty, 636 currentFloor: unit.currentFloor !== null ? unit.currentFloor : 'unset', 637 floorT1: computeFloor(e[0], false), 638 floorT2: computeFloor(e[0], true), 639 recentWins: unit.recentWinCpms.length, 640 pendingUpdates: pendingCount, 641 nextFlushInMs: msUntilFlush 642 }; 643 }) 644 }; 645 }, 646 647 reset: function () { stats = freshStats(); save(); console.log('[BidManager] Reset'); }, 648 649 report: function () { 650 var s = this.getStats(); 651 console.group('[BidManager] Phase: ' + s.phase + ', Wins: ' + s.totalWins + 652 ', Country: ' + s.country + ', Floor min: $' + s.countryFloor + 653 ', Group: ' + s.group); 654 console.table(s.bidders); 655 console.table(s.adUnits); 656 console.groupEnd(); 657 } 658 }; 659})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.