prebid.js 6.4.0 → 6.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/modules/adyoulikeBidAdapter.js +13 -9
  2. package/modules/bliinkBidAdapter.js +1 -1
  3. package/modules/consentManagement.js +7 -1
  4. package/modules/docereeBidAdapter.js +10 -1
  5. package/modules/docereeBidAdapter.md +2 -0
  6. package/modules/gptPreAuction.js +55 -7
  7. package/modules/ixBidAdapter.js +29 -12
  8. package/modules/limelightDigitalBidAdapter.js +2 -1
  9. package/modules/livewrappedAnalyticsAdapter.js +3 -1
  10. package/modules/loglyliftBidAdapter.js +79 -0
  11. package/modules/loglyliftBidAdapter.md +55 -0
  12. package/modules/optimeraRtdProvider.js +8 -1
  13. package/modules/ozoneBidAdapter.js +21 -64
  14. package/modules/yieldmoBidAdapter.js +23 -5
  15. package/modules/zeta_global_sspAnalyticsAdapter.js +97 -0
  16. package/modules/zeta_global_sspAnalyticsAdapter.md +24 -0
  17. package/package.json +1 -1
  18. package/src/config.js +27 -3
  19. package/src/prebid.js +2 -0
  20. package/src/utils.js +12 -1
  21. package/test/spec/config_spec.js +279 -0
  22. package/test/spec/modules/adyoulikeBidAdapter_spec.js +49 -0
  23. package/test/spec/modules/consentManagement_spec.js +20 -0
  24. package/test/spec/modules/docereeBidAdapter_spec.js +9 -1
  25. package/test/spec/modules/gptPreAuction_spec.js +177 -2
  26. package/test/spec/modules/ixBidAdapter_spec.js +104 -62
  27. package/test/spec/modules/limelightDigitalBidAdapter_spec.js +75 -17
  28. package/test/spec/modules/livewrappedAnalyticsAdapter_spec.js +22 -0
  29. package/test/spec/modules/loglyliftBidAdapter_spec.js +172 -0
  30. package/test/spec/modules/optimeraRtdProvider_spec.js +14 -1
  31. package/test/spec/modules/ozoneBidAdapter_spec.js +43 -31
  32. package/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js +427 -0
@@ -180,11 +180,13 @@ function getCanonicalUrl() {
180
180
 
181
181
  /* Get mediatype from bidRequest */
182
182
  function getMediatype(bidRequest) {
183
+ if (deepAccess(bidRequest, 'mediaTypes.banner')) {
184
+ return BANNER;
185
+ }
183
186
  if (deepAccess(bidRequest, 'mediaTypes.video')) {
184
187
  return VIDEO;
185
- } else if (deepAccess(bidRequest, 'mediaTypes.banner')) {
186
- return BANNER;
187
- } else if (deepAccess(bidRequest, 'mediaTypes.native')) {
188
+ }
189
+ if (deepAccess(bidRequest, 'mediaTypes.native')) {
188
190
  return NATIVE;
189
191
  }
190
192
  }
@@ -345,7 +347,7 @@ function getTrackers(eventsArray, jsTrackers) {
345
347
 
346
348
  function getVideoAd(response) {
347
349
  var adJson = {};
348
- if (typeof response.Ad === 'string') {
350
+ if (typeof response.Ad === 'string' && response.Ad.indexOf('\/\*PREBID\*\/') > 0) {
349
351
  adJson = JSON.parse(response.Ad.match(/\/\*PREBID\*\/(.*)\/\*PREBID\*\//)[1]);
350
352
  return deepAccess(adJson, 'Content.MainVideo.Vast');
351
353
  }
@@ -478,13 +480,15 @@ function createBid(response, bidRequests) {
478
480
  meta: response.Meta || { advertiserDomains: [] }
479
481
  };
480
482
 
481
- if (request && request.Native) {
483
+ // retreive video response if present
484
+ const vast64 = response.Vast || getVideoAd(response);
485
+ if (vast64) {
486
+ bid.vastXml = window.atob(vast64);
487
+ bid.mediaType = 'video';
488
+ } else if (request.Native) {
489
+ // format Native response if Native was requested
482
490
  bid.native = getNativeAssets(response, request.Native);
483
491
  bid.mediaType = 'native';
484
- } else if (request && request.Video) {
485
- const vast64 = response.Vast || getVideoAd(response);
486
- bid.vastXml = vast64 ? window.atob(vast64) : '';
487
- bid.mediaType = 'video';
488
492
  } else {
489
493
  bid.width = response.Width;
490
494
  bid.height = response.Height;
@@ -1,6 +1,6 @@
1
1
  // eslint-disable-next-line prebid/validate-imports
2
2
  // eslint-disable-next-line prebid/validate-imports
3
- import {registerBidder} from 'src/adapters/bidderFactory.js'
3
+ import {registerBidder} from '../src/adapters/bidderFactory.js'
4
4
 
5
5
  export const BIDDER_CODE = 'bliink'
6
6
  export const BLIINK_ENDPOINT_ENGINE = 'https://engine.bliink.io/delivery'
@@ -371,7 +371,13 @@ function processCmpData(consentObject, hookConfig) {
371
371
  * General timeout callback when interacting with CMP takes too long.
372
372
  */
373
373
  function cmpTimedOut(hookConfig) {
374
- cmpFailed('CMP workflow exceeded timeout threshold.', hookConfig);
374
+ if (cmpVersion === 2) {
375
+ logWarn(`No response from CMP, continuing auction...`)
376
+ storeConsentData(undefined);
377
+ exitModule(null, hookConfig)
378
+ } else {
379
+ cmpFailed('CMP workflow exceeded timeout threshold.', hookConfig);
380
+ }
375
381
  }
376
382
 
377
383
  /**
@@ -14,6 +14,13 @@ export const spec = {
14
14
  const { placementId } = bid.params;
15
15
  return !!placementId
16
16
  },
17
+ isGdprConsentPresent: (bid) => {
18
+ const { gdpr, gdprConsent } = bid.params;
19
+ if (gdpr == '1') {
20
+ return !!gdprConsent
21
+ }
22
+ return true
23
+ },
17
24
  buildRequests: (validBidRequests) => {
18
25
  const serverRequests = [];
19
26
  const { data } = config.getConfig('doceree.user')
@@ -21,7 +28,7 @@ export const spec = {
21
28
  const encodedUserInfo = window.btoa(encodeURIComponent(JSON.stringify(data)))
22
29
 
23
30
  validBidRequests.forEach(function(validBidRequest) {
24
- const { publisherUrl, placementId } = validBidRequest.params;
31
+ const { publisherUrl, placementId, gdpr, gdprConsent } = validBidRequest.params;
25
32
  const url = publisherUrl || page
26
33
  let queryString = '';
27
34
  queryString = tryAppendQueryString(queryString, 'id', placementId);
@@ -32,6 +39,8 @@ export const spec = {
32
39
  queryString = tryAppendQueryString(queryString, 'prebidjs', true);
33
40
  queryString = tryAppendQueryString(queryString, 'token', token);
34
41
  queryString = tryAppendQueryString(queryString, 'requestId', validBidRequest.bidId);
42
+ queryString = tryAppendQueryString(queryString, 'gdpr', gdpr);
43
+ queryString = tryAppendQueryString(queryString, 'gdpr_consent', gdprConsent);
35
44
 
36
45
  serverRequests.push({
37
46
  method: 'GET',
@@ -25,6 +25,8 @@ var adUnits = [
25
25
  params: {
26
26
  placementId: 'DOC_7jm9j5eqkl0xvc5w', //required
27
27
  publisherUrl: document.URL || window.location.href, //optional
28
+ gdpr: '1', //optional
29
+ gdprConsent:'CPQfU1jPQfU1jG0AAAENAwCAAAAAAAAAAAAAAAAAAAAA.IGLtV_T9fb2vj-_Z99_tkeYwf95y3p-wzhheMs-8NyZeH_B4Wv2MyvBX4JiQKGRgksjLBAQdtHGlcTQgBwIlViTLMYk2MjzNKJrJEilsbO2dYGD9Pn8HT3ZCY70-vv__7v3ff_3g', //optional
28
30
  }
29
31
  }
30
32
  ]
@@ -48,10 +48,35 @@ const sanitizeSlotPath = (path) => {
48
48
  return path;
49
49
  }
50
50
 
51
+ const defaultPreAuction = (adUnit, adServerAdSlot) => {
52
+ const context = adUnit.ortb2Imp.ext.data;
53
+
54
+ // use pbadslot if supplied
55
+ if (context.pbadslot) {
56
+ return context.pbadslot;
57
+ }
58
+
59
+ // confirm that GPT is set up
60
+ if (!isGptPubadsDefined()) {
61
+ return;
62
+ }
63
+
64
+ // find all GPT slots with this name
65
+ var gptSlots = window.googletag.pubads().getSlots().filter(slot => slot.getAdUnitPath() === adServerAdSlot);
66
+
67
+ if (gptSlots.length === 0) {
68
+ return; // should never happen
69
+ }
70
+
71
+ if (gptSlots.length === 1) {
72
+ return adServerAdSlot;
73
+ }
74
+
75
+ // else the adunit code must be div id. append it.
76
+ return `${adServerAdSlot}#${adUnit.code}`;
77
+ }
78
+
51
79
  export const appendPbAdSlot = adUnit => {
52
- adUnit.ortb2Imp = adUnit.ortb2Imp || {};
53
- adUnit.ortb2Imp.ext = adUnit.ortb2Imp.ext || {};
54
- adUnit.ortb2Imp.ext.data = adUnit.ortb2Imp.ext.data || {};
55
80
  const context = adUnit.ortb2Imp.ext.data;
56
81
  const { customPbAdSlot } = _currentConfig;
57
82
 
@@ -84,11 +109,32 @@ export const appendPbAdSlot = adUnit => {
84
109
 
85
110
  export const makeBidRequestsHook = (fn, adUnits, ...args) => {
86
111
  appendGptSlots(adUnits);
112
+ const { useDefaultPreAuction, customPreAuction } = _currentConfig;
87
113
  adUnits.forEach(adUnit => {
88
- const usedAdUnitCode = appendPbAdSlot(adUnit);
89
- // gpid should be set to itself if already set, or to what pbadslot was (as long as it was not adUnit code)
90
- if (!adUnit.ortb2Imp.ext.gpid && !usedAdUnitCode) {
91
- adUnit.ortb2Imp.ext.gpid = adUnit.ortb2Imp.ext.data.pbadslot;
114
+ // init the ortb2Imp if not done yet
115
+ adUnit.ortb2Imp = adUnit.ortb2Imp || {};
116
+ adUnit.ortb2Imp.ext = adUnit.ortb2Imp.ext || {};
117
+ adUnit.ortb2Imp.ext.data = adUnit.ortb2Imp.ext.data || {};
118
+ const context = adUnit.ortb2Imp.ext;
119
+
120
+ // if neither new confs set do old stuff
121
+ if (!customPreAuction && !useDefaultPreAuction) {
122
+ const usedAdUnitCode = appendPbAdSlot(adUnit);
123
+ // gpid should be set to itself if already set, or to what pbadslot was (as long as it was not adUnit code)
124
+ if (!context.gpid && !usedAdUnitCode) {
125
+ context.gpid = context.data.pbadslot;
126
+ }
127
+ } else {
128
+ let adserverSlot = deepAccess(context, 'data.adserver.adslot');
129
+ let result;
130
+ if (customPreAuction) {
131
+ result = customPreAuction(adUnit, adserverSlot);
132
+ } else if (useDefaultPreAuction) {
133
+ result = defaultPreAuction(adUnit, adserverSlot);
134
+ }
135
+ if (result) {
136
+ context.gpid = context.data.pbadslot = result;
137
+ }
92
138
  }
93
139
  });
94
140
  return fn.call(this, adUnits, ...args);
@@ -100,6 +146,8 @@ const handleSetGptConfig = moduleConfig => {
100
146
  'customGptSlotMatching', customGptSlotMatching =>
101
147
  typeof customGptSlotMatching === 'function' && customGptSlotMatching,
102
148
  'customPbAdSlot', customPbAdSlot => typeof customPbAdSlot === 'function' && customPbAdSlot,
149
+ 'customPreAuction', customPreAuction => typeof customPreAuction === 'function' && customPreAuction,
150
+ 'useDefaultPreAuction', useDefaultPreAuction => useDefaultPreAuction === true,
103
151
  ]);
104
152
 
105
153
  if (_currentConfig.enabled) {
@@ -53,7 +53,12 @@ const SOURCE_RTI_MAPPING = {
53
53
  'neustar.biz': 'fabrickId',
54
54
  'zeotap.com': 'zeotapIdPlus',
55
55
  'uidapi.com': 'UID2',
56
- 'adserver.org': 'TDID'
56
+ 'adserver.org': 'TDID',
57
+ 'id5-sync.com': '', // ID5 Universal ID, configured as id5Id
58
+ 'crwdcntrl.net': '', // Lotame Panorama ID, lotamePanoramaId
59
+ 'epsilon.com': '', // Publisher Link, publinkId
60
+ 'audigent.com': '', // Halo ID from Audigent, haloId
61
+ 'pubcid.org': '' // SharedID, pubcid
57
62
  };
58
63
  const PROVIDERS = [
59
64
  'britepoolid',
@@ -69,7 +74,8 @@ const PROVIDERS = [
69
74
  'quantcastId',
70
75
  'pubcid',
71
76
  'TDID',
72
- 'flocId'
77
+ 'flocId',
78
+ 'pubProvidedId'
73
79
  ];
74
80
  const REQUIRED_VIDEO_PARAMS = ['mimes', 'minduration', 'maxduration']; // note: protocol/protocols is also reqd
75
81
  const VIDEO_PARAMS_ALLOW_LIST = [
@@ -194,7 +200,7 @@ function bidToImp(bid) {
194
200
  imp.id = bid.bidId;
195
201
 
196
202
  imp.ext = {};
197
- imp.ext.siteID = bid.params.siteId;
203
+ imp.ext.siteID = bid.params.siteId.toString();
198
204
 
199
205
  if (bid.params.hasOwnProperty('id') &&
200
206
  (typeof bid.params.id === 'string' || typeof bid.params.id === 'number')) {
@@ -444,16 +450,19 @@ function getEidInfo(allEids, flocData) {
444
450
  let seenSources = {};
445
451
  if (isArray(allEids)) {
446
452
  for (const eid of allEids) {
447
- if (SOURCE_RTI_MAPPING[eid.source] && deepAccess(eid, 'uids.0')) {
453
+ if (SOURCE_RTI_MAPPING.hasOwnProperty(eid.source) && deepAccess(eid, 'uids.0')) {
448
454
  seenSources[eid.source] = true;
449
- eid.uids[0].ext = {
450
- rtiPartner: SOURCE_RTI_MAPPING[eid.source]
451
- };
455
+ if (SOURCE_RTI_MAPPING[eid.source] != '') {
456
+ eid.uids[0].ext = {
457
+ rtiPartner: SOURCE_RTI_MAPPING[eid.source]
458
+ };
459
+ }
452
460
  delete eid.uids[0].atype;
453
461
  toSend.push(eid);
454
462
  }
455
463
  }
456
464
  }
465
+
457
466
  const isValidFlocId = flocData && flocData.id && flocData.version;
458
467
  if (isValidFlocId) {
459
468
  const flocEid = {
@@ -466,6 +475,7 @@ function getEidInfo(allEids, flocData) {
466
475
 
467
476
  return { toSend, seenSources };
468
477
  }
478
+
469
479
  /**
470
480
  * Builds a request object to be sent to the ad server based on bid requests.
471
481
  *
@@ -680,6 +690,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) {
680
690
 
681
691
  if (impressionObjects.length && BANNER in impressionObjects[0]) {
682
692
  const { id, banner: { topframe }, ext } = impressionObjects[0];
693
+ const gpid = impressions[transactionIds[adUnitIndex]].gpid;
683
694
  const _bannerImpression = {
684
695
  id,
685
696
  banner: {
@@ -688,10 +699,10 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) {
688
699
  },
689
700
  }
690
701
 
691
- if (ext.dfp_ad_unit_code) {
692
- _bannerImpression.ext = {
693
- dfp_ad_unit_code: ext.dfp_ad_unit_code
694
- }
702
+ if (ext.dfp_ad_unit_code || gpid) {
703
+ _bannerImpression.ext = {};
704
+ _bannerImpression.ext.dfp_ad_unit_code = ext.dfp_ad_unit_code;
705
+ _bannerImpression.ext.gpid = gpid;
695
706
  }
696
707
 
697
708
  if ('bidfloor' in impressionObjects[0]) {
@@ -911,6 +922,7 @@ function createBannerImps(validBidRequest, missingBannerSizes, bannerImps) {
911
922
  bannerImps[validBidRequest.transactionId].ixImps = []
912
923
  }
913
924
  bannerImps[validBidRequest.transactionId].ixImps.push(imp);
925
+ bannerImps[validBidRequest.transactionId].gpid = deepAccess(validBidRequest, 'ortb2Imp.ext.gpid');
914
926
  }
915
927
 
916
928
  if (ixConfig.hasOwnProperty('detectMissingSizes') && ixConfig.detectMissingSizes) {
@@ -1186,7 +1198,12 @@ export const spec = {
1186
1198
  }
1187
1199
 
1188
1200
  if (typeof bid.params.siteId !== 'string' && typeof bid.params.siteId !== 'number') {
1189
- logError('IX Bid Adapter: siteId must be string or number value.', { bidder: BIDDER_CODE, code: ERROR_CODES.SITE_ID_INVALID_VALUE });
1201
+ logError('IX Bid Adapter: siteId must be string or number type.', { bidder: BIDDER_CODE, code: ERROR_CODES.SITE_ID_INVALID_VALUE });
1202
+ return false;
1203
+ }
1204
+
1205
+ if (typeof bid.params.siteId !== 'string' && isNaN(Number(bid.params.siteId))) {
1206
+ logError('IX Bid Adapter: siteId must valid value', { bidder: BIDDER_CODE, code: ERROR_CODES.SITE_ID_INVALID_VALUE });
1190
1207
  return false;
1191
1208
  }
1192
1209
 
@@ -159,7 +159,8 @@ function buildPlacement(bidRequest) {
159
159
  }
160
160
  }),
161
161
  type: bidRequest.params.adUnitType.toUpperCase(),
162
- publisherId: bidRequest.params.publisherId
162
+ publisherId: bidRequest.params.publisherId,
163
+ userIdAsEids: bidRequest.userIdAsEids
163
164
  }
164
165
  }
165
166
  }
@@ -114,6 +114,7 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE
114
114
  let wonBid = cache.auctions[args.auctionId].bids[args.requestId];
115
115
  wonBid.won = true;
116
116
  wonBid.floorData = args.floorData;
117
+ wonBid.rUp = args.rUp;
117
118
  if (wonBid.sendStatus != 0) {
118
119
  livewrappedAnalyticsAdapter.sendEvents();
119
120
  }
@@ -288,7 +289,8 @@ function getWins(gdpr, auctionIds) {
288
289
  auctionId: auctionIdPos,
289
290
  auc: bid.auc,
290
291
  buc: bid.buc,
291
- lw: bid.lw
292
+ lw: bid.lw,
293
+ rUp: bid.rUp
292
294
  });
293
295
  }
294
296
  });
@@ -0,0 +1,79 @@
1
+ import { config } from '../src/config.js';
2
+ import { registerBidder } from '../src/adapters/bidderFactory.js';
3
+ import { NATIVE } from '../src/mediaTypes.js';
4
+
5
+ const BIDDER_CODE = 'loglylift';
6
+ const ENDPOINT_URL = 'https://bid.logly.co.jp/prebid/client/v1';
7
+
8
+ export const spec = {
9
+ code: BIDDER_CODE,
10
+ supportedMediaTypes: [NATIVE],
11
+
12
+ isBidRequestValid: function (bid) {
13
+ return !!(bid.params && bid.params.adspotId);
14
+ },
15
+
16
+ buildRequests: function (bidRequests, bidderRequest) {
17
+ const requests = [];
18
+ for (let i = 0, len = bidRequests.length; i < len; i++) {
19
+ const request = {
20
+ method: 'POST',
21
+ url: ENDPOINT_URL + '?adspot_id=' + bidRequests[i].params.adspotId,
22
+ data: JSON.stringify(newBidRequest(bidRequests[i], bidderRequest)),
23
+ options: {},
24
+ bidderRequest
25
+ };
26
+ requests.push(request);
27
+ }
28
+ return requests;
29
+ },
30
+
31
+ interpretResponse: function (serverResponse, { bidderRequest }) {
32
+ serverResponse = serverResponse.body;
33
+ const bidResponses = [];
34
+ if (!serverResponse || serverResponse.error) {
35
+ return bidResponses;
36
+ }
37
+ serverResponse.bids.forEach(function (bid) {
38
+ bidResponses.push(bid);
39
+ })
40
+ return bidResponses;
41
+ },
42
+
43
+ getUserSyncs: function (syncOptions, serverResponses) {
44
+ const syncs = [];
45
+
46
+ if (syncOptions.iframeEnabled && serverResponses.length > 0) {
47
+ syncs.push({
48
+ type: 'iframe',
49
+ url: 'https://sync.logly.co.jp/sync/sync.html'
50
+ });
51
+ }
52
+ return syncs;
53
+ }
54
+
55
+ };
56
+
57
+ function newBidRequest(bid, bidderRequest) {
58
+ const currencyObj = config.getConfig('currency');
59
+ const currency = (currencyObj && currencyObj.adServerCurrency) || 'USD';
60
+
61
+ return {
62
+ auctionId: bid.auctionId,
63
+ bidderRequestId: bid.bidderRequestId,
64
+ transactionId: bid.transactionId,
65
+ adUnitCode: bid.adUnitCode,
66
+ bidId: bid.bidId,
67
+ mediaTypes: bid.mediaTypes,
68
+ params: bid.params,
69
+ prebidJsVersion: '$prebid.version$',
70
+ url: window.location.href,
71
+ domain: config.getConfig('publisherDomain'),
72
+ referer: bidderRequest.refererInfo.referer,
73
+ auctionStartTime: bidderRequest.auctionStart,
74
+ currency: currency,
75
+ timeout: config.getConfig('bidderTimeout')
76
+ };
77
+ }
78
+
79
+ registerBidder(spec);
@@ -0,0 +1,55 @@
1
+ # Overview
2
+ ```
3
+ Module Name: LOGLY lift for Publisher
4
+ Module Type: Bidder Adapter
5
+ Maintainer: dev@logly.co.jp
6
+ ```
7
+
8
+ # Description
9
+ Module that connects to Logly's demand sources.
10
+ Currently module supports only native mediaType.
11
+
12
+ # Test Parameters
13
+ ```
14
+ var adUnits = [
15
+ // Native adUnit
16
+ {
17
+ code: 'test-native-code',
18
+ sizes: [[1, 1]],
19
+ mediaTypes: {
20
+ native: {
21
+ title: {
22
+ required: true
23
+ },
24
+ image: {
25
+ required: true
26
+ },
27
+ sponsoredBy: {
28
+ required: true
29
+ }
30
+ }
31
+ },
32
+ bids: [{
33
+ bidder: 'loglylift',
34
+ params: {
35
+ adspotId: 4302078
36
+ }
37
+ }]
38
+ }
39
+ ];
40
+ ```
41
+
42
+ # UserSync example
43
+
44
+ ```
45
+ pbjs.setConfig({
46
+ userSync: {
47
+ filterSettings: {
48
+ iframe: {
49
+ bidders: '*', // '*' represents all bidders
50
+ filter: 'include'
51
+ }
52
+ }
53
+ }
54
+ });
55
+ ```
@@ -173,7 +173,9 @@ export function setScoresURL() {
173
173
  }
174
174
 
175
175
  /**
176
- * Set the scores for the divice if given.
176
+ * Set the scores for the device if given.
177
+ * Add any any insights to the winddow.optimeraInsights object.
178
+ *
177
179
  * @param {*} result
178
180
  * @returns {string} JSON string of Optimera Scores.
179
181
  */
@@ -184,6 +186,11 @@ export function setScores(result) {
184
186
  if (device !== 'default' && scores.device[device]) {
185
187
  scores = scores.device[device];
186
188
  }
189
+ logInfo(scores);
190
+ if (scores.insights) {
191
+ window.optimeraInsights = window.optimeraInsights || {};
192
+ window.optimeraInsights.data = scores.insights;
193
+ }
187
194
  } catch (e) {
188
195
  logError('Optimera score file could not be parsed.');
189
196
  }