Methodology / colophon
Numbers before claims.
This page names the source, the timing, the known gaps, and the public export surface behind People Not Numbers. A reader should be able to cite an aggregate without guessing where it came from.
records in the local mirror
sentence rows parsed from source profiles
Michigan counties represented
§ source
Source
The primary source is the Michigan Department of Corrections OTIS public search and profile system.1 The scraper records the current OTIS flow first, then falls back to the legacy profile URL when that is the only route that returns a record. The mirror keeps the profile source URL, the scrape timestamp, and the parsed sentence fields.
| Primary source | Michigan Department of Corrections OTIS public profile records. |
|---|---|
| Current flow | GET /otis2/Search, POST /otis2/Search, then POST /OTIS2/Results to load the profile page. |
| Legacy fallback | OTIS2/otis2profile.aspx?mdocNumber={zero-padded MDOC number}. |
| Local mirror | SQLite offenders.db, with source_url and scraped_at retained for every mirrored profile. |
| Photos | 107,772 local photo files, served only after opt-in. |
The public site uses aggregates from the mirror. It does not present the mirror as a court file, a live custody check, or a complete case history. OTIS is an administrative source. That fact is useful. It is also a limit.
§ cadence
Cadence
The local database shows a bulk scrape window from 2026-03-05 to 2026-03-13. A bounded refresh run finished at 2026-05-14T00:57:52Z; the newest row-level scrape timestamp observed locally is 2026-05-13T20:57:51.424308. Aggregate statistics were recomputed at 2026-06-01T01:20:52Z.
That means the dashboard is not a live feed. The citable date for aggregate figures is the stats recompute timestamp. For individual custody, supervision, or release questions, check OTIS directly before publication.
§ aggregates
Aggregates + Census baselines
The current stats payload contains 142,592 records, 520,719 sentence rows, 83 counties, and 31,849 records whose current status is canonicalized as Prisoner. The race comparison columns use Census ACS 5-year data profiles where baseline percentages are shown.2
Baselines are denominators, not explanations. A difference between an OTIS aggregate and an ACS baseline is the start of a reporting question. It is not a causal claim by itself.
- totalsRecord, sentence, county, custody, and sentence-per-person totals.
- raceStatewide OTIS race aggregate with the statewide Census baseline column.
- statusCanonicalized current-status counts from the public mirror.
- ageAge buckets computed from date-of-birth fields at stats build time.
- genderMale and female counts as published by the source record.
- convictionPlea, trial, nolo contendere, and unknown conviction-method counts.
- sentencingMinimum, maximum, median, and life-or-40-plus sentence summary fields.
- decadeSentence-row volume grouped by sentence decade.
- countiesTop county sentence counts with metro labels and Census baseline fields.
- county-raceTop county race shares with county Census baseline columns.
- facilitiesCurrent-prisoner facility counts from source location fields.
- offensesTop offense labels with local category assignment.
- sentence-lengthMinimum and maximum sentence buckets from parsed sentence rows.
- census-baselinesStatewide and top-county baseline percentages used for comparison.
§ known gaps
Known gaps
County-jail populations are outside the source. Juvenile and youth records are outside the source. Sealed, expunged, removed, corrected, or delayed records may be absent or stale. The mirror also inherits OTIS field choices: race categories are source categories, offense labels are source labels, and some sentence rows do not carry the same detail as others.
The county and facility views are aggregate views. They should not be read as the full legal geography of a person's case. They are useful for scale and distribution. They are not a docket.
§ privacy defaults
Privacy defaults
Photos are hidden by default. Birth dates are reduced to year-level display where the public interface does not need the full date. Marks, scars, and tattoos are treated as sensitive descriptive fields, not browse material. Lookup pages are excluded from indexing; aggregate and methodology pages are indexable.
These defaults do not make a public record private. They make publication less casual. The design choice is to reveal only what the reader deliberately asks to see.
§ corrections
Corrections
Corrections, photo-removal requests, and source disputes go to [email protected]. Include the MDOC number, the field in dispute, and a public source or document if one exists. If the subject is a person, the first obligation is right-of-reply.
Corrections are handled at the interface and source-note level first. If the source itself changes, the next refresh should carry that change through to the aggregate layer.
§ reproducibility
Reproducibility
Every public aggregate exposed on this page has a CSV route under /api/exports/{slug}. Each row carries computed_at. The route returns the current stats payload when the local FastAPI service is available and falls back to the checked-in launch fixture when it is not.
The build script that produces the stats payload walks offenders.db, parses the sentence JSON, canonicalizes status and race fields, and writes stats.json. The public CSVs are aggregate exports only. They are not a normalized row-level republication of every person in the mirror.
§ last reviewed
Last reviewed
This methodology page was last reviewed on 2026-05-29. The stamp is derived from the latest git commit touching this page, with a static fallback for builds that do not have git metadata.