It’s time for a reckoning about criminal intelligence databases.

Natalie Matthews-Ramo This article is part of the Policing and Technology Project, a collaboration between

An illustration of a social network shows lines connecting people and files.
Natalie Matthews-Ramo

This article is part of the Policing and Technology Project, a collaboration between Future Tense and the Tech, Law, & Security Program at American University Washington College of Law that examines the relationship between law enforcement, police reform, and technology. On Sept. 18 at noon Eastern, Future Tense will host “Power, Policing, and Tech,” an online event about the role of technology in law enforcement reform. For more information and to RSVP, visit the New America website.

Public scrutiny around data-driven technologies in the criminal justice system has been on a steady rise over the past few years, but with the recent widespread Black Lives Matter mobilization, it has reached a crescendo. Alongside a broader reckoning with the harms of the criminal justice system, technologies like facial recognition and predictive policing have been called out as racist systems that need to be dismantled. After being an early adopter of predictive policing, the Santa Cruz, California, became the first city in the United States to ban its use. An ethics committee of a police department in the United Kingdom unanimously rejected a proposal for the department to further develop an artificial intelligence system to predict gun and knife crime. And the use of pre-trial and sentencing risk assessments remain at the center of public debate on how to best address mass incarceration and racial disparities within the criminal justice system.

But a foundational piece of police technology is missing from this reckoning: criminal intelligence databases. They may be largely absent from the public debate because databases are typically considered simple record repositories, often seen as the “first stage” in the creation of more high-tech A.I. systems. But these databases perform varied and advanced functions of profiling, not unlike systems of predictive policing. The historical context and political ramifications of these systems also mirror the systematic stigmatization and “feedback loop” that is now commonly understood as a fallout of predictive A.I. systems.

Unlike investigative databases that are used to solve serious crimes and build prosecutors’ cases, criminal intelligence databases are populated with information about people who should be monitored and subjected to greater scrutiny because they might commit a future crime—for example, the notorious No Fly List created and maintained by the FBI’s Terrorist Screening Center, which prohibits people from boarding commercial aircraft for travel within, into, or out of the United States based on government threat assessments. Yet these databases are often seen as passive aids for information gathering, rather than new methods of surveillance, which has contributed to the lack of legal safeguards. In the U.S., for example, these databases do not need to comply with the same constitutional and legal standards that govern criminal investigations, like due process and freedom of association. These databases are heavily influenced by politics and public sentiments, and their composition and use often reflect the prerogatives and biases of law enforcement agencies.

Gang databases serve as a great example for understanding these complexities. They have been around for decades, but their use has expanded globally as a crime-fighting tool in recent years. Defining what constitutes a gang or who is a gang member, however, is not as clear-cut as one may think. Who (and what kinds of information) is included in a gang database is typically guided by formal or informal policies that provide a definition of “gang” or “gang members.” But in the United States the legal definitions of gangs and gang members are so inconsistent that someone who meets the definition of a gang member in one state may not be considered a gang member in a neighboring state. There is also no consensus on what constitutes gang activity, since gang membership is not a criminal offense in itself. The lack of clear guidance and rules means police officers have a lot of discretion in making such determinations.

Does making a gang hand symbol in a social media post mean that the individual is a gang member? Are explicit or violent rap lyrics evidence of gang or criminal activity? Does frequenting areas where gang members are known to meet warrant inclusion in a gang database? Without clear gang database policies, police officers more often than not rely on subjective judgments and stereotypes to determine gang members and gang activity. And research and reporting on gang databases have shown that these judgments on who and what to include in gang databases reflect a historic pattern of overpolicing Black, Latinx, and other racial and ethnic minority communities. The NYPD’s gang database is 99 percent Black and Latinx residents, the same demographic of residents targeted by the department’s unconstitutional stop-and-frisk program. In London, 78 percent of the Metropolitan Police’s Gang Matrix database are young Black men, even though the department’s own figures show that this demographic only makes up 27 percent of youth violence.

The history of criminalizing entire groups through database technologies far predates the digital. Current gang database practices in the U.K. have been linked to the British colonial strategy of criminalizing entire communities (designated “criminal tribes”) in India. In the United States, federal and local law enforcement have repeatedly created watchlists of political activists without any evidence of criminal activity; when criminal charges did arise they were primarily loosely constructed conspiracy cases. The more recent digitization of databases only amplifies the problems of stigmatization and hyper-surveillance. Digitized and networked databases can more easily break down the silos between different government agencies, allowing for more seamless information sharing, and as a result— more pervasive institutional profiling that can be used to justify differential treatment. Gang database designations are often shared with not only prosecutors, judges, and prison and jail officials but also schools, public housing authorities, immigration agencies, and employers. This means that the impact of being labelled and sorted in these systems carries significant and unique consequences for individuals and communities, far beyond the criminal justice system. Individuals identified in gang databases are subjected to increased police scrutiny and harassment, but so are their family members, neighbors, and other individuals that share any characteristics (such as race, age, height, and gender presentation). All of these negative consequences are based on the risk or assumption that individuals in databases will commit a crime, even when individuals have no prior criminal convictions.

Despite being functionally similar to predictive policing and other risk assessment tools, gang databases don’t seem to benefit from the degree of regulatory scrutiny and media attention afforded to other purportedly “new” technologies.  To illustrate, even as the Chicago Police Department announced it will no longer use its controversial predictive policing program, it proudly announced it would be revamping its much-criticized gang database. If a gang database designation leads a judge to deny a defendant bail, how is that functionally different from a biased risk-assessment tool’s recommendation? If gang databases perform data analysis and inform government decision making, why are they seen as more elementary than other criminal justice technologies?

The policy interventions needed for criminal intelligence databases also mirror some of the advocacy demands being made of A.I. systems. For example, just as mandatory “algorithmic audits” or “algorithmic impact assessments” are gaining traction as a way to allow external researchers and advocates to interrogate the logics and data used in A.I. systems, advocates are demanding similar pathways to access the logic and contents of gang databases. As the public begins to reject “new” forms of police technology before they are entrenched, we must not miss the opportunity to question the legitimacy of legacy technologies, too.

Sign up for the Future Tense newsletter, published every other Saturday.

Future Tense
is a partnership of
Slate,
New America, and
Arizona State University
that examines emerging technologies, public policy, and society.

nnx3c!-- Rubicon Project Ad Tag --x3en

nn

nn")), n = v(f[r.size_id].split("x").map(function (e)
return Number(e);
), 2), i.width = n[0], i.height = n[1]), i.rubiconTargeting = (Array.isArray(r.targeting) ? r.targeting : []).reduce(function (e, r)
return e[r.key] = r.values[0], e;
,
rpfl_elemid: s.adUnitCode
), e.push(i)) : g.logError("Rubicon: bidRequest undefined at index position:".concat(t), c, d), e;
, []).sort(function (e, r) 0);
);
},
getUserSyncs: function getUserSyncs(e, r, t, i)
if (!R && e.iframeEnabled)
var n = "";
return t && "string" == typeof t.consentString && ("boolean" == typeof t.gdprApplies ? n += "?gdpr=".concat(Number(t.gdprApplies), "&gdpr_consent=").concat(t.consentString) : n += "?gdpr_consent=".concat(t.consentString)), i && (n += "".concat(n ? "&" : "?"https://slate.com/technology/2020/09/,"us_privacy=").concat(encodeURIComponent(i))), R = !0,
type: "iframe",
url: a + n
;

,
transformBidParams: function transformBidParams(e)
return g.convertTypes(
accountId: "number",
siteId: "number",
zoneId: "number"
, e);

};

function _(e, r) a.privacy && a.privacy.optout) return null;
var s = (p(t = , o.id, a.id), p(t, o.keyv, a.keyv), t);
return o.pref && (s[o.pref] = 0), s;

function I(e, r) r.refererInfo.referer;
return e.params.secure ? t.replace(/^https:/i, "https:") : t;

function A(e, r)
var t = e.params;

if ("video" === r)
var i = [];
return t.video && t.video.playerWidth && t.video.playerHeight ? i = [t.video.playerWidth, t.video.playerHeight] : Array.isArray(g.deepAccess(e, "mediaTypes.video.playerSize")) && 1 === e.mediaTypes.video.playerSize.length ? i = e.mediaTypes.video.playerSize[0] : Array.isArray(e.sizes) && 0 < e.sizes.length && Array.isArray(e.sizes[0]) && 1 < e.sizes[0].length && (i = e.sizes[0]), i; var n = []; return Array.isArray(t.sizes) ? n = t.sizes : void 0 !== g.deepAccess(e, "mediaTypes.banner.sizes") ? n = s(e.mediaTypes.banner.sizes) : Array.isArray(e.sizes) && 0 < e.sizes.length ? n = s(e.sizes) : g.logWarn("Rubicon: no sizes are setup or found"), S(n); function s(e) return g.parseSizesInput(e).reduce(function (e, r) var t = parseInt(f[r], 10); return t && e.push(t), e; , []); function c(e) return "object" === x(g.deepAccess(e, "params.video")) && void 0 !== g.deepAccess(e, "mediaTypes.".concat(u.d)); function m(e, r) var t = 1 < arguments.length && void 0 !== r && r; return c(e) ? -1 === ["outstream"https://slate.com/technology/2020/09/,"instream"].indexOf(g.deepAccess(e, "mediaTypes.".concat(u.d, ".context"))) ? void (t && g.logError("Rubicon: mediaTypes.video.context must be outstream or instream")) : A(e, "video").length < 2 ? void (t && g.logError("Rubicon: could not determine the playerSize of the video")) : (t && g.logMessage("Rubicon: making video request for adUnit", e.adUnitCode), "video") : 0 === A(e, "banner").length ? void (t && g.logError("Rubicon: could not determine the sizes for banner request")) : (t && g.logMessage("Rubicon: making banner request for adUnit", e.adUnitCode), "banner"); function S(e) var n = [15, 2, 9]; return e.sort(function (e, r) ); function C(e) var r = parseInt(g.deepAccess(e, "params.video.size_id")); return isNaN(r) ? "outstream" === g.deepAccess(e, "mediaTypes.".concat(u.d, ".context")) ? 203 : 201 : r; function j(e) return ranges: low: [ max: 5, increment: .5 ], medium: [ max: 20, increment: .1 ], high: [ max: 20, increment: .01 ], auto: [ max: 5, increment: .05 , min: 5, max: 10, increment: .1 , min: 10, max: 20, increment: .5 ], dense: [ max: 3, increment: .01 , min: 3, max: 8, increment: .05 , min: 8, max: 20, increment: .5 ], custom: e.getConfig("customPriceBucket") && e.getConfig("customPriceBucket").buckets [e.getConfig("priceGranularity")] ; function k(r) var t = !0, e = Object.prototype.toString.call([]), i = Object.prototype.toString.call(0), n = mimes: e, protocols: e, maxduration: i, linearity: i, api: e ; return Object.keys(n).forEach(function (e) Object.prototype.toString.call(g.deepAccess(r, "mediaTypes.video." + e)) !== n[e] && (t = !1, g.logError("Rubicon: mediaTypes.video." + e + " is required and must be of type: " + n[e])); ), t; function T(e) var r = !1, t = ["asi"https://slate.com/technology/2020/09/,"sid"https://slate.com/technology/2020/09/,"hp"]; return e.nodes && ((r = e.nodes.reduce(function (e, r) return e ? t.every(function (e) return r[e]; ) : e; , !0)) function w(e, r) return "rp_schain" === e ? "rp_schain=".concat(r) : "".concat(e, "=").concat(encodeURIComponent(r)); var R = !1; Object(i.registerBidder)(h); } }, [677]); pbjsChunk([93], { 719: function _(e, t, r) e.exports = r(720); , 720: function _(e, t, r) { "use strict"; Object.defineProperty(t, "__esModule", value: !0 ), r.d(t, "spec", function () return o; ), r.d(t, "_isInbounds", function () return a; ), t._getPlatform = v; var n = r(1), y = r(0), i = r(2), c = r(3), g = r(10), d = r(44); function h(e, t) function s(e, t) (null == t function u() function (e) for (var t = 1; t < arguments.length; t++) var r = arguments[t]; for (var n in r) Object.prototype.hasOwnProperty.call(r, n) && (e[n] = r[n]); return e; ).apply(this, arguments); function p(e, t, r) return t in e ? Object.defineProperty(e, t, value: r, enumerable: !0, configurable: !0, writable: !0 ) : e[t] = r, e; var b = "sonobi", l = Object(y.generateUUID)(), o = { code: b, supportedMediaTypes: [i.b, i.d], isBidRequestValid: function isBidRequestValid(e) if (!e.params) return !1; if (!e.params.ad_unit && !e.params.placement_id) return !1; if (!Object(y.deepAccess)(e, "mediaTypes.banner") && !Object(y.deepAccess)(e, "mediaTypes.video")) return !1; if (Object(y.deepAccess)(e, "mediaTypes.banner")) if (!Object(y.deepAccess)(e, "mediaTypes.banner.sizes") && !e.params.sizes) return !1; else if (Object(y.deepAccess)(e, "mediaTypes.video")) if ("outstream" === Object(y.deepAccess)(e, "mediaTypes.video.context") && !e.params.sizes) return !1; if ("instream" === Object(y.deepAccess)(e, "mediaTypes.video.context") && !Object(y.deepAccess)(e, "mediaTypes.video.playerSize")) return !1; return !0; , buildRequests: function buildRequests(e, t) var r = e.map(function (e) var t = function (e) if (e.params.ad_unit) return e.params.ad_unit; return e.params.placement_id; (e); return /^[/]?[d]+[[/].+[/]?]?$/.test(t) ? (t = "/" === t.charAt(0) ? t : "/" + t, p(, "".concat(t, "), n = ; r.forEach(function (e) u(n, e); ); var i = key_maker: JSON.stringify(n), ref: t.refererInfo.referer, s: Object(y.generateUUID)(), pv: l, vp: v(), lib_name: "prebid", lib_v: "3.23.0", us: 0 ; c.b.getConfig("userSync") && c.b.getConfig("userSync").syncsPerBidder && (i.us = c.b.getConfig("userSync").syncsPerBidder), d.a.canBidderRegisterSync("iframe", b) ? i.ius = 1 : i.ius = 0, Object(y.deepAccess)(e[0], "params.hfa") && (i.hfa = Object(y.deepAccess)(e[0], "params.hfa")), e[0].params.referrer && (i.ref = e[0].params.referrer), t && t.gdprConsent && (i.gdpr = t.gdprConsent.gdprApplies ? "true" : "false", t.gdprConsent.consentString && (i.consent_string = t.gdprConsent.consentString)); var s = function (t) e.privacy && e.privacy.optout) return null; return e; ("fhnS5drwmH"); s && (i.digid = s.id, i.digkeyv = s.keyv), e[0].schain && (i.schain = JSON.stringify(e[0].schain)), Object(y.deepAccess)(e[0], "userId") && 0 < Object.keys(e[0].userId).length && (i.userid = JSON.stringify(e[0].userId)); var o = e[0].params.keywords; if (o && (i.kw = o), t && t.uspConsent && (i.us_privacy = t.uspConsent), Object(y.isEmpty)(n)) return null; var a = "https://apex.go.sonobi.com/trinity.json"; return Object(y.deepAccess)(e[0], "params.bid_request_url") && (a = Object(y.deepAccess)(e[0], "params.bid_request_url")), method: "GET", url: a, withCredentials: !0, data: i, bidderRequests: e ; , interpretResponse: function interpretResponse(e, l) { var f = e.body, m = [], v = l.data.ref; return 0 === Object.keys(f.slots).length || Object.keys(f.slots).forEach(function (e) ").slice(-1)[0], n = function (e, t) for (var r = 0; r < e.length; r++) if (e[r].bidId === t) return e[r]; (l.bidderRequests, r), i = null; "video" === t.sbi_ct && (i = "video"https://slate.com/technology/2020/09/,"outstream" === Object(y.deepAccess)(n, "mediaTypes.video.context") && (i = "outstream")); var s, o, a, c, d, u, p, b = j(i, v); t.sbi_aid && t.sbi_mouse && t.sbi_size && (a = void 0 === (o = (s = h(t.sbi_size.split("x"), 2))[0]) ? 1 : o, d = void 0 === (c = s[1]) ? 1 : c, u = , t.sbi_dozer && (u.dealId = t.sbi_dozer), "video" === i ? (u.mediaType = "video", u.vastUrl = b(f.sbi_dc, t.sbi_aid), delete u.ad, delete u.width, delete u.height) : "outstream" === i && n && (u.mediaType = "video", u.vastUrl = b(f.sbi_dc, t.sbi_aid), u.renderer = function (e, t) var r = 2 < arguments.length && void 0 !== arguments[2] ? arguments[2] : , n = g.a.install( id: t.aid, url: "https://mtrx.go.sonobi.com/sbi_outstream_renderer.js", config: r, loaded: !1, adUnitCode: e ); try n.setRender(O); catch (e) Object(y.logWarn)("Prebid Error calling setRender on renderer", e); return n.setEventHandlers( impression: function impression() return Object(y.logMessage)("Sonobi outstream video impression event"); , loaded: function loaded() return Object(y.logMessage)("Sonobi outstream video loaded event"); , ended: function ended() Object(y.logMessage)("Sonobi outstream renderer video event"); ), n; (n.adUnitCode, u, Object(y.deepAccess)(n, "renderer.options")), p = Object(y.deepAccess)(n, "params.sizes"), Array.isArray(p) && Array.isArray(p[0]) && (p = p[0]), p && (u.width = p[0], u.height = p[1])), m.push(u)); ), m; }, getUserSyncs: function getUserSyncs(e, t) var r = []; try e.pixelEnabled && t[0].body.sbi_px.forEach(function (e) r.push( type: e.type, url: e.url ); ); catch (e) return r; }; function f(e) return Object(y.deepAccess)(e, "mediaTypes.video") ? "" : e.params.sizes ? Object(y.parseSizesInput)(e.params.sizes).join(",") : Object(y.deepAccess)(e, "mediaTypes.banner.sizes") ? Object(y.parseSizesInput)(Object(y.deepAccess)(e, "mediaTypes.banner.sizes")).join(",") : e.sizes ? Object(y.parseSizesInput)(e.sizes).join(",") : void 0; function m(e) f=".concat(e.params.floor) : ""; var j = function j(i, s) return function (e, t) ; ; var a = function a(e) var r = 0 < arguments.length && void 0 !== e ? e : window; return function () var e = 0 < arguments.length && void 0 !== arguments[0] ? arguments[0] : 0, t = 1 < arguments.length && void 0 !== arguments[1] ? arguments[1] : Number.MAX_SAFE_INTEGER; return r.innerWidth >= e && r.innerWidth < t; ; ; function v() var e = 0 < arguments.length && void 0 !== arguments[0] ? arguments[0] : window, t = a(e), r = 992, n = 768; return t(0, 768) ? "mobile" : t(n, r) ? "tablet" : "desktop"; function O(i) i.renderer.push(function () var e = h(i.getSize().split("x"), 2), t = e[0], r = e[1], n = new window.SbiOutstreamRenderer(); n.init( vastUrl: i.vastUrl, height: r, width: t ), n.setRootElement(i.adUnitCode); ); Object(n.registerBidder)(o); } }, [719]); pbjsChunk([85], { 741: function _(e, r, t) e.exports = t(742); , 742: function _(e, r, t) { "use strict"; Object.defineProperty(r, "__esModule", value: !0 ), t.d(r, "spec", function () return n; ); var a = t(1), d = t(0), p = 12, o = 11, c = 0, u = 22, n = code: "teads", supportedMediaTypes: ["video"https://slate.com/technology/2020/09/,"banner"], isBidRequestValid: function isBidRequestValid(e) d.logError("Teads placementId and pageId parameters are required. Bid aborted."), a; , buildRequests: function buildRequests(e, r) var t = e.map(I), a = referrer: function (e) var r = ""; e && e.refererInfo && e.refererInfo.referer && (r = e.refererInfo.referer); return r; (r), data: t, deviceWidth: screen.width, hb_version: "3.23.0" ; e[0].schain && (a.schain = e[0].schain); var n, d, i, s = r.gdprConsent; return r && s && (n = "boolean" == typeof s.gdprApplies, d = "string" == typeof s.consentString, i = n ? function (e, r, t) var a = p; e ? function (e, r) e.hasGlobalConsent : !(!e (r, t) && (a = o) : a = c; return a; (s.gdprApplies, s.vendorData, s.apiVersion) : u, a.gdpr_iab = consent: d ? s.consentString : "", status: i, apiVersion: s.apiVersion ), r && r.uspConsent && (a.us_privacy = r.uspConsent), method: "POST", url: "https://a.teads.tv/hb/bid-request", data: JSON.stringify(a) ; , interpretResponse: function interpretResponse(e) var t = []; return (e = e.body).responses && e.responses.forEach(function (e) var r = cpm: e.cpm, width: e.width, height: e.height, currency: e.currency, netRevenue: !0, ttl: e.ttl, ad: e.ad, requestId: e.bidId, creativeId: e.creativeId, placementId: e.placementId ; e.dealId && (r.dealId = e.dealId), t.push(r); ), t; ; function I(e) var r, t = , a = d.getValue(e.params, "placementId"), n = d.getValue(e.params, "pageId"); return t.sizes = (r = e, d.parseSizesInput(function (e) (r))), t.bidId = d.getBidIdParameter("bidId", e), t.bidderRequestId = d.getBidIdParameter("bidderRequestId", e), t.placementId = parseInt(a, 10), t.pageId = parseInt(n, 10), t.adUnitCode = d.getBidIdParameter("adUnitCode", e), t.auctionId = d.getBidIdParameter("auctionId", e), t.transactionId = d.getBidIdParameter("transactionId", e), t; function i(e) return 0 < parseInt(e); Object(a.registerBidder)(n); } }, [741]); pbjsChunk([76], { 761: function _(r, e, t) r.exports = t(762); , 762: function _(r, e, t) { "use strict"; Object.defineProperty(e, "__esModule", value: !0 ), t.d(e, "tripleliftAdapterSpec", function () return s; ); var n = t(2), i = t(1), o = t(0), u = t(3); function p(r) function d(r, e) var a = !0, c = null, s = { code: "triplelift", supportedMediaTypes: [n.b], isBidRequestValid: function isBidRequestValid(r) return void 0 !== r.params.inventoryCode; , buildRequests: function buildRequests(r, e) { var t, n = "https://tlx.3lift.com/header/auction?", i = function (r) { var e = , t = r[0].schain; e.imp = r.map(function (r, e) { return id: e, tagid: r.params.inventoryCode, floor: r.params.floor, banner: format: r.sizes.filter(l).map(function (r) return w: r[0], h: r[1] ; ) ; }); var n = [].concat(p(function (r) return f(r, "tdid"https://slate.com/technology/2020/09/,"adserver.org"https://slate.com/technology/2020/09/,"TDID"); (r)), p(function (r) return f(r, "idl_env"https://slate.com/technology/2020/09/,"liveramp.com"https://slate.com/technology/2020/09/,"idl"); (r)), p(function (r) return f(r, "criteoId"https://slate.com/technology/2020/09/,"criteo.com"https://slate.com/technology/2020/09/,"criteoId"); (r))); 0 < n.length && (e.user = ext: eids: n ); t && (e.ext = schain: t ); return e; }(r); return n = o.tryAppendQueryString(n, "lib"https://slate.com/technology/2020/09/,"prebid"), n = o.tryAppendQueryString(n, "v"https://slate.com/technology/2020/09/,"3.23.0"), e && e.refererInfo && (t = e.refererInfo.referer, n = o.tryAppendQueryString(n, "referrer", t)), e && e.timeout && (n = o.tryAppendQueryString(n, "tmax", e.timeout)), e && e.gdprConsent && (void 0 !== e.gdprConsent.gdprApplies && (a = e.gdprConsent.gdprApplies, n = o.tryAppendQueryString(n, "gdpr", a.toString())), void 0 !== e.gdprConsent.consentString && (c = e.gdprConsent.consentString, n = o.tryAppendQueryString(n, "cmp_cs", c))), e && e.uspConsent && (n = o.tryAppendQueryString(n, "us_privacy", e.uspConsent)), !0 === u.b.getConfig("coppa") && (n = o.tryAppendQueryString(n, "coppa", !0)), n.lastIndexOf("&") === n.length - 1 && (n = n.substring(0, n.length - 1)), o.logMessage("tlCall request built: " + n), method: "POST", url: n, data: i, bidderRequest: e ; }, interpretResponse: function interpretResponse(r, e) var t = e.bidderRequest; return (r.body.bids , getUserSyncs: function getUserSyncs(r, e, t, n) var i = function (r) if (!r) return; if (r.iframeEnabled) return "iframe"; if (r.pixelEnabled) return "image"; (r); if (i) var u = "https://eb2.3lift.com/sync?"; return "image" === i && (u = o.tryAppendQueryString(u, "px", 1), u = o.tryAppendQueryString(u, "src"https://slate.com/technology/2020/09/,"prebid")), null !== c && (u = o.tryAppendQueryString(u, "gdpr", a), u = o.tryAppendQueryString(u, "cmp_cs", c)), n && (u = o.tryAppendQueryString(u, "us_privacy", n)), [ type: i, url: u ]; }; function f(r, e, t, n) { return r.map((o = e, function (r) return r && r.userId && r.userId[o]; )).filter(function (r) return !!r; ).map((i = t, u = n, function (r) return source: i, uids: [ id: r, ext: rtiPartner: u ] ; )); var i, u, o; } function l(r) return 2 === r.length && "number" == typeof r[0] && "number" == typeof r[1]; Object(i.registerBidder)(s); } }, [761]); pbjsChunk([75], { 763: function _(e, r, t) e.exports = t(764); , 764: function _(e, r, t) { "use strict"; Object.defineProperty(r, "__esModule", value: !0 ), t.d(r, "spec", function () return v; ); var m = t(0), s = t(1), o = t(10), c = t(2), f = "Bid from response has no auid parameter - ", l = "Bid from response has no adm parameter - ", p = "Array of bid objects is empty", g = "Can't find in requested bids the bid with auid - ", u = "Seatbid array from response has empty item", y = "Response is empty", b = "Response has empty seatbid array", h = "Seatbid from response has no array of bid objects - ", v = { code: "trustx", supportedMediaTypes: [c.b, c.d], isBidRequestValid: function isBidRequestValid(e) return !!e.params.uid; , buildRequests: function buildRequests(e, r) var a, o, p = [], u = , c = , f = , l = "net"; (e , interpretResponse: function interpretResponse(e, r, t) { var s = 2 < arguments.length && void 0 !== t ? t : o.a; e = e && e.body; var n, d = [], i = r.bidsMap, a = r.data.pt; return e ? e.seatbid && !e.seatbid.length && (n = b) : n = y, !n && e.seatbid && e.seatbid.forEach(function (e) { !function (e, d, r, t, s) if (!e) return; var n; e.auid (function (e) e ? e.bid ? e.bid[0] (e), i, a, d, s); }), n && m.logError(n), d; }, getUserSyncs: function getUserSyncs(e) if (e.pixelEnabled) return [ type: "image", url: "https://sofia.trustx.org/push_sync" ]; }; function E(e) var r; r = e.value, m.isArray(r) && 0 < r.length && "" === e.value[0] && delete e.value; function w(e) e.renderer.push(function () window.ANOutstreamVideo.renderAd( targetId: e.adUnitCode, adResponse: e.adResponse ); ); Object(s.registerBidder)(v); } }, [763]); pbjs.processQueue(); }, ]; window.modules["via.legacy"] = [function(require,module,exports){"use strict"; DS.service("via", function () { "use strict"; // remove `via` from url, to be used after amplitude logs it to prevent users from sharing such urls function removeFromLocation() var url = new URL(location.href); url.searchParams.delete("via"); history.replaceState(null, "", url.toString()); // and add `via` param to any outbound links function addViaToUrl(href, via) href.substr(0, 1) === "#") return href; // don't add to jumps on the current page, e.g. "Skip to main content" var url = new URL(href); var apexDomain = new URL(location.href).hostname.split(".").slice(-2).join("."); if (url.hostname.indexOf(apexDomain) === -1) return href; // don't add it to external links url.searchParams.set("via", via); return url.toString(); // keys correspond to "page_types" in editable_components.yml var PREFIXES = article: "article", homepage: "homepage", "vertical front": "section", "rubric front": "rubric" ; var pageType; function setPageType(amplitudePageType) pageType = PREFIXES[amplitudePageType]; var DELIMITER = "_"; function concatVia(node, via) var tag = node.dataset && node.dataset.via; if (tag) via = (via.length ? tag + DELIMITER : tag) + via; return via; function addToClickedLinks() document.documentElement.addEventListener("click", function (e) var a; var via = ""; // detect link nodes and collect via directives to append to the href var node = e.target; // the element where this event originated may have been removed from the dom, e.g. in the case of the OIL CMP which disappears after you interact with it while (node && node !== e.currentTarget) if (node.tagName === "A") a = node; via = concatVia(node, via); node = node.parentNode; if (a && via) if (pageType) via = pageType + DELIMITER + via; a.href = addViaToUrl(a.href, via); ); function addToSubmittedForms() document.documentElement.addEventListener("submit", function (e) var form = e.target; // collect via directives var via = ""; var node = e.target; while (node !== e.currentTarget) via = concatVia(node, via); node = node.parentNode; if (via) if (pageType) via = pageType + DELIMITER + via; // dynamically create a hidden input for the form url var input = document.createElement("input"); input.type = "hidden"; input.name = "via"; input.value = via; form.appendChild(input); ); // start listening only once, when first injected addToClickedLinks(); addToSubmittedForms(); return setPageType: setPageType, removeFromLocation: removeFromLocation ; }); }, ]; window.modules["visibility.legacy"] = [function(require,module,exports){"use strict"; DS.service("$visibility", ["$document"https://slate.com/technology/2020/09/,"$window"https://slate.com/technology/2020/09/,"_throttle"https://slate.com/technology/2020/09/,"Eventify", function ($document, $window, _throttle, Eventify) var list = [], Visible, VisibleEvent; /** * @param number a * @param number b * @returns * * @see https://jsperf.com/math-min-vs-if-condition-vs/8 */ function min(a, b) return a < b ? a : b; /** * @param number a * @param number b * @returns * * @see https://jsperf.com/math-min-vs-if-condition-vs/8 */ function max(a, b) return a > b ? a : b;

/**
* Fast loop through watched elements
*/

function onScroll()
list.forEach(updateVisibility);

/**
* updates seen property
* @param Visble item
* @param evt
* @fires Visible#shown
* @fires Visible#hidden
*/

function updateSeen(item, evt) percent < item.hiddenThreshold) && item.seen) item.seen = false; setTimeout(function () item.trigger("hidden", new VisibleEvent("hidden", evt)); , 15); /** * sets preload property * @param Visible item * @param evt * @param Number innerHeight * @fires Visible#preload */ function updatePreload(item, evt, innerHeight) if (!item.preload && item.preloadThreshhold && shouldBePreloaded(evt.target, evt.rect, item.preloadThreshhold, innerHeight)) item.preload = true; setTimeout(function () item.trigger("preload", new VisibleEvent("preload", evt)); , 15); /** * Trigger events * @param Visible item */ function updateVisibility(item) /** * Return normalized viewport height * @return number */ function getViewportHeight() $document.documentElement.clientHeight /** * Return normalized viewport width * @return number */ function getViewportWidth() $document.body.clientWidth; /** * make sure an element isn't hidden by styles or etc * @param Element el * @return Boolean */ function isElementNotHidden(el) return el && el.offsetParent !== null && !el.getAttribute("hidden") && getComputedStyle(el).display !== "none" && getComputedStyle(el).visibility !== "hidden"; /** * Apparently the fastest way... * @param Element el * @returns boolean * @example if (!$visibility.isElementInViewport(el)) ... */ function isElementInViewport(el) var rect = el.getBoundingClientRect(); return rect.top >= 0 && rect.left >= 0 && rect.bottom <= ($window.innerHeight /** * @param Element el * @param ClientRect rect * @param Number preloadThreshhold * @param Number innerHeight * @return Boolean */ function shouldBePreloaded(el, rect, preloadThreshhold, innerHeight) return rect.top <= innerHeight + preloadThreshhold && isElementNotHidden(el); /** * Create a one-dimensional spacial hash of x * @param number x * @param number stepSize * @param number optimalK * @param number base * @return number */ function getLinearSpacialHash(x, stepSize, optimalK, base) var index = Math.floor(x / (stepSize /** * @param ClientRect rect * @param number innerHeight * @returns number */ function getVerticallyVisiblePixels(rect, innerHeight) return min(innerHeight, max(rect.bottom, 0)) - min(max(rect.top, 0), innerHeight); /** * Get offset of element relative to entire page * * @param Element el * @returns left: number, top: number * @see https://jsperf.com/offset-vs-getboundingclientrect/7 */ function getPageOffset(el) var offsetLeft = el.offsetLeft, offsetTop = el.offsetTop; while (el = el.offsetParent) offsetLeft += el.offsetLeft; offsetTop += el.offsetTop; return left: offsetLeft, top: offsetTop ; /** * Create a new Visible class to observe when elements enter and leave the viewport * * Call destroy function to stop listening (this is until we have better support for watching for Node Removal) * @param Element el * @param shownThreshold: number, hiddenThreshold: number [options] * @class * @example this.visible = new $visibility.Visible(el); */ Visible = function Visible(el, options) ; Visible.prototype = /** * Stop triggering. */ destroy: function destroy() // remove from list list.splice(list.indexOf(this), 1); /** * @name Visible#on * @function * @param 'hidden' e EventName * @param function cb Callback */ /** * @name Visible#trigger * @function * @param 'hidden' e * @param */ ; Eventify.enable(Visible.prototype); VisibleEvent = function VisibleEvent(type, options) var _this = this; this.type = type; Object.keys(options).forEach(function (key) _this[key] = options[key]; ); ; // listen for scroll events (throttled) $document.addEventListener("scroll", _throttle(onScroll, 200)); // public this.getPageOffset = getPageOffset; this.getLinearSpacialHash = getLinearSpacialHash; this.getVerticallyVisiblePixels = getVerticallyVisiblePixels; this.getViewportHeight = getViewportHeight; this.getViewportWidth = getViewportWidth; this.isElementNotHidden = isElementNotHidden; this.isElementInViewport = isElementInViewport; this.Visible = Visible; ]); }, ]; require=(function e(t,n,r){function s(o,u){if(!n[o])if(!t[o])var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",fvar l=n[o]=exports:;t[o][0].call(l.exports,function(e)var n=t[o][1][e];return s(n?n:e),l,l.exports,e,t,n,r)return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o