How Justice Lens works
1 · The data pipeline
Everything on this site is computed from public records published by the DC government through DC Open Data. There are five sources: MPD's stop data collected under the NEAR Act (two published layers covering July 2019 through 2022 and 2023 to present, about 509,000 stops of persons), MPD adult arrests (2013 through 2025, about 320,000 records), the MPD reported-incident feed (the nine Part I victim and property offenses, about 588,000 incidents since 2008), monthly moving-violation datasets (roughly 98.6% camera-issued, which is what makes the camera-versus-officer comparison possible), and ACS 2019-2023 five-year population estimates assigned to police districts by census-tract centroid.
The incident feed is the only source that moves daily, and it is served from two places. Completed years live in a Postgres archive, ingested once and then treated as immutable history. The in-progress year is fetched live from MPD's ArcGIS FeatureServer, and a daily ingest job also writes it into Postgres so the archive is never more than a day behind. That ingest re-fetches a rolling 14-day window rather than just yesterday: rows are upserted on their MPD-assigned incident number, so re-fetching is idempotent, MPD's own post-publication corrections (reclassified offenses, corrected coordinates) are picked up, and the pipeline self-heals after an outage of up to two weeks instead of permanently dropping the gap. A separate daily job compares MPD's live record count against the archive and emails the operator when drift exceeds 5%, so a silent divergence cannot persist.
"Computed live on page load" means exactly that: the analysis pages carry no hand-entered numbers. When a page is requested, the server runs the analysis queries against the latest finished ingest snapshot and renders the results into the HTML it returns. If a source has not changed, the numbers do not change; when MPD revises a record, the next day's page reflects it. The one deliberate exception is the search-threshold model, whose MCMC fit is too heavy for request time: it is fitted offline, its posterior is stored, and the page renders the stored fit alongside checks of that fit against the observed data.
2 · The nine analyses
Each analysis pairs one enforcement measure with an explicit baseline, because a count with no denominator is an insinuation, not a finding. Stops by district compares each police district's share of Black stops against that district's own Black population share (a local index, not the citywide average, so a district cannot look equitable merely by resembling the city). The disparity over time is the same measure computed year by year from 2019 through 2025, so a persistent pattern can be distinguished from a one-year artifact. The search outcome test compares how often searches of each group actually find contraband, which shifts the baseline from who was searched to what the searches produced. Arrests by district applies the stop methodology to thirteen years of adult arrest records. Camera versus officer enforcement uses automated cameras as a race-blind control: cameras record where violations happen, officers choose where to write tickets, and the gap between those two geographies is the measure. The veil-of-darkness test and the search-threshold test are described in their own sections below. Enforcement versus reported crime replaces the population denominator entirely: it measures each district's share of stops, frisks, and arrests against its share of victim-initiated reported crime, so the strongest counter-hypothesis, that police simply go where crime is, is tested rather than assumed. Under that baseline, stop and arrest volume roughly track reported violence; frisks exceed every baseline. Both results are published at the same prominence. The ninth analysis, classification mix over time, is different in kind: it audits the reliability of the incident archive itself at two separately scoped boundaries, the 2013 loss of independent review documented by the 2026 OIG inspection, and the 2023–2025 period where House, DOJ, and MPD internal affairs findings document classification manipulation, with directional and reversion tests built from the documented downgrade paths.
3 · Why veil-of-darkness
The core problem with any stop-rate comparison is the exposure population: nobody knows who was actually on the road, in view of an officer, available to be stopped. Residential population is a proxy, and every critic of disparity work attacks the proxy. The veil-of-darkness design (Grogger and Ridgeway, 2006) sidesteps it. The clock time of sunset moves through the year while human routines stay anchored to the clock, so the same 6:30 pm commute happens in daylight in June and in darkness in December. If officers can see race before deciding to stop a driver in daylight but largely cannot in darkness, then a stop process that uses race should produce a Blacker mix of daylight stops than darkness stops at the same clock time. The driving population at a fixed clock time is its own control; no external population estimate enters the test.
The implementation restricts to the inter-twilight window, the band of clock times that experiences both daylight and darkness across the year, and compares within 30-minute clock strata using a Mantel-Haenszel odds ratio, so 6:00 pm stops are only ever compared with other 6:00 pm stops. A district-stratified variant separates lighting from deployment geography, and a variant confined to 30-day windows around the daylight-saving transitions holds season nearly constant while lighting changes by an hour in a week.
Its known limitations are stated on the page rather than footnoted away. The test only speaks to the inter-twilight window, evening hours, and says nothing about noon or 3 am stops. Dusk itself involves selection: the drivers on the road at 6 pm in December are not identically the drivers at 6 pm in June, and weather, holidays, and school calendars move with season. Street lighting means darkness does not fully mask race, which biases the test toward finding nothing. And it is a test of the stop decision only; it cannot see what happens after the stop.
4 · Why Bayesian search-threshold modeling
Raw stop-rate comparisons cannot distinguish discrimination from a difference in the underlying distribution of behavior that officers respond to. The outcome test partially escapes this by looking at results: if searches of one group find contraband at a lower rate, officers were evidently applying a lower evidence bar to that group. But the outcome test has a known failure mode, infra-marginality: two groups can have equal hit rates while facing different thresholds, or unequal hit rates under identical thresholds, because the hit rate averages over everyone searched rather than measuring the marginal person an officer was just barely willing to search.
The threshold test (Simoiu, Corbett-Davies and Goel, 2017) models that margin directly. For each district and race group it treats the officer's decision as a cutoff on a latent signal of guilt, and jointly infers the signal distribution and the group-specific threshold from the observed search rates and hit rates across districts. A lower inferred threshold for one group means it took less evidence to trigger a search of that group, which is a statement about the decision standard, not about the population. The model is fitted offline by MCMC; the site stores the posterior and publishes its fit quality against the observed rates, so a reader can see where the model does and does not reproduce the data.
The method is contested on real grounds, and the page says so: identification rests on parametric assumptions about the latent signal distribution, results can be sensitive to priors and to how districts are pooled, and unrecorded search outcomes have to be excluded, which assumes their absence is unrelated to race. The site treats the threshold test as one instrument among nine, cross-checked against the outcome test it refines, not as an oracle.
5 · The map query architecture
The incident map accepts free-text questions ("robberies near Union Station after
midnight in 2024") and turns them into structured queries over the 588,000+ record
incident archive. The language model never touches the data. It is given a single
tool, fetch_crimes, whose JSON schema is the entire query surface: a
mode (last 30 days, a single year, or a year-by-year animation), a year or year
range, a ward (1-8), a neighborhood cluster, a comma-separated offense list drawn
from the nine MPD Part I offense strings, an optional address with a radius in
miles, and a time-of-day filter (a named shift or an explicit hour window). The
model's job is translation: it reads the question, writes a short confirmation
sentence, and emits one tool call.
Everything the model emits is then validated server-side against whitelists the model cannot influence: unknown offense strings, wards outside 1-8, unrecognized cluster names, years outside 2008 to the current year, and radii over 7 miles are each rejected with an explicit error rather than coerced into a nearby guess. Addresses are geocoded with a DC-bounded geocoder; a place the geocoder cannot resolve inside the District yields no coordinates rather than a best-effort pin somewhere else. When the model cannot map a question to the schema at all it makes no tool call, and the user gets a plain "I couldn't interpret that query" with examples, never a silently wrong map. The resolved filters are also shown back in the interface after every query, so the translation step is inspectable and correctable rather than a black box.
User: "robberies near Union Station after midnight in 2024"
Resolved tool call (fetch_crimes):
{
"mode": "year",
"year": "2024",
"offenses": "ROBBERY",
"address": "Union Station",
"radius_miles": 0.5,
"shift": "MIDNIGHT"
}
Applied map query (after server-side geocoding):
{
"mode": "year", "year": "2024",
"offenses": "ROBBERY",
"address": "Union Station",
"radius_miles": 0.5,
"lat": 38.8973, "lng": -77.0063,
"shift": "MIDNIGHT"
}
Signed-out visitors see one precomputed sample of this feature: the response for the default map view (all offense types, last 30 days) is generated once per day, right after the data ingest it describes, and served from storage. No model call happens at request time for anonymous traffic, and the sample is stamped with its generation time.
6 · Limitations, and what this data cannot support
Reported crime is not occurred crime: reporting rates vary by neighborhood and offense, and the incident feed contains only the nine victim and property offenses, so officer-generated categories like possession are absent from that denominator by construction. MPD stop records carry no coordinates and no vehicle-versus-pedestrian flag, so stop geography is district-grained and the traffic-stop analyses rely on a ticket-only proxy. Residential population is not exposure population; the crime baseline and the veil test exist precisely because that proxy is weak. MPD's race categories and the ACS's are different measurement instruments. None of the nine analyses measures individual officer intent, and none supports ranking neighborhoods by safety, which is why the site refuses to do either.
The reported-crime data itself carries a documented reliability caveat. In July 2026 the DC Inspector General found that MPD's crime classification and reporting controls broke down over more than a decade: the independent unit that reviewed crime reports before they became official statistics was disbanded around 2013 and never replaced, and approved reports could be edited with minimal oversight (OIG No. 26-E-03-FA0, July 28, 2026). The OIG's process review found weak controls; separate House, DOJ, and MPD internal affairs findings (December 2025 and May 2026) document case-level manipulation of classifications for 2023–2025. Every figure here with a reported-crime denominator inherits MPD's classification decisions for roughly 2013 through the 2026 reforms; the classification-mix analysis tests the archive at both boundaries.
The same discipline applies where the data favors MPD, and it sometimes does: searches find contraband at statistically indistinguishable rates by race, and stop and arrest volume roughly track reported violence under the crime baseline. Those results are published with the same prominence as the disparities, because the credibility of the method depends on reporting whatever it returns.
Go deeper: Methodology | All nine analyses | The incident map