Methodology

Opinion release estimator

Every pending case carries a predicted opinion-release window. Here is exactly how it's built — and the running scorecard that keeps it honest.

79.0%
of opinions land inside the 80% window
target: 80%
±45d
median error of the point estimate
was ±94d
2.1k
decided cases the model learns from
+111 still-pending
live
the window re-centres as a case ages
no re-fit needed

What the card predicts

Once a case is argued, the only honest answer to “when will the opinion come down?” is a range. So each pending case page shows a 10/50/90 date band: the model’s best single date, bracketed by an interval built to contain the real release date 80% of the time — no more (false precision), no less (a window you can’t trust).

The hard part: the target keeps moving

Predicting opinion timing isn’t one problem — it’s a different problem every day. A case argued last week and a case that’s been pending fourteen months are not two points on the same curve; the second has already survived long enough to rule out every quick disposition. A naive “average case takes N days” model is most wrong precisely when you most want an answer: on the slow, interesting cases.

The fix is to treat this as a survival problem. The model predicts a whole distribution of plausible release dates, then re-conditions it on the simple fact that no opinion has issued yet. As a case ages without a decision, the window doesn’t just sit there going stale — it slides forward, automatically:

argument+100d+200dtoday · pending 100dday-0 p50 · 62dp10 · 105dp50 · 132dp90 · 237d
The full curve is the predicted time-to-opinion for a representative case the day it’s argued. Once it has been pending 100 days with no opinion (gold line), everything in the shaded region is ruled out — so the model renormalises over the remaining mass and the 10/50/90 window slides forward from ~62d to 105–237d. No re-fitting required: it’s a closed-form conditional, evaluated on your clock at page load.

The model

A log-logistic accelerated-failure-time (AFT) model, fit on every prior decided case — plus every currently-pending case folded in as right-censored evidence (we don’t know its release date yet, but we know it’s already taken at least this long, and throwing that away would bias every estimate fast). The closed-form survival curve is what makes the conditional re-centring above a one-line evaluation at render time rather than a re-fit.

What it reads per case
  • Subject area — habeas moves unlike a tax appeal.
  • Panel — a per-triad intercept for the three judges sitting.
  • Authoring judge — a per-judge tempo intercept. Until the opinion issues, the estimate mixes over the three panelists and a per-curiam disposition.
  • Term cycle — sin/cos of the argument date (end-of-term rushes).
  • Court drift — a linear year term for the court’s changing pace.
  • Panel-day load + a per-curiam flag for short unsigned dispositions.
  • Scheduling flags — rehearing, law-school sitting, a judge visiting by designation.

Categorical effects (judge, subject, triad) are partially pooled: the model is fit as a hierarchical Bayesian survival model in NumPyro (variational inference over a JAX log-density), where each block carries its own learned between-group scale σ. A block whose levels barely differ shrinks toward the global tempo; a block with real signal keeps its spread — adaptively, per block, instead of the single hand-tuned ridge penalty the earlier lifelines fit could only approximate. A thin cohort is pulled toward the average instead of over-fitting three cases, and a rich one is left free to speak.

authorσ = 0.35subjectσ = 0.04triadσ = 0.04larger σ ⇒ that block’s levels were left freer to differ

Does it actually work?

A prediction interval is only worth showing if it’s honest, so the model grades itself the hard way. The harness walks 18 monthly cutoffs, and at each one it re-fits on cases decided before the cutoff, predicts the cases still pending then, and scores those predictions against what eventually happened — never letting the model see the future. Each scored case is predicted exactly as the case page would have shown it that day: no authoring judge, no per-curiam flag. The newest cutoff sits a full year before the data ends, so the slow cases have had time to resolve, and a case that still hasn’t resolved is scored rather than dropped: once it has outlived its window, it counts as a miss. 2,683 out-of-sample predictions, from 2,246 cases (2,134 decided). This is the same scorecard that gates every data refresh: if a new fit doesn’t clear the coverage target, it doesn’t ship.

Coverage: do the windows mean what they say?

The old estimator was a hand-tuned cascade of cohort medians. Its “80% window” actually contained the answer 9.4% of the time — dramatic over-confidence. The survival model hits 79.0% against an 80% target. Pinball loss, the standard interval score, fell from 118d to 34d.

50% windowtarget 50.0%
55.3%
5.8%
80% window · the one the card showstarget 80.0%
79.0%
9.4%
95% windowtarget 95.0%
90.9%
20.8%
AFT survival model (now) Old cascade nominal target

Calibration across the whole distribution

fast resolutionsslow resolutions
Where each case’s eventual release date fell inside its own predicted distribution. A perfectly calibrated model is flat — the dashed line is that ideal. The bars track it closely across the whole range, which is what lets the same model serve an 80% window and a 95% window honestly. The residual lift at the edges is the model’s known tendency to under-cover its very fastest and very slowest cases by a few points.

…and it holds for a case’s entire life

0-30 days
83.3% vs 17.5%
30-90 days
82.4% vs 19.1%
90-180 days
80.4% vs 1.4%
180-365 days
68.4% vs 0.0%
365+ days
68.8% vs 0.0%
80% window coverage, split by how long a case had been pending when it was scored (emerald = survival model, grey ghost = old cascade; vertical line is the 80% target). The cascade fell to zero coverage past six months — it simply had no way to predict a case that had outlived its own point estimate. The survival model holds the target across the entire life of a case, which is exactly the regime that matters: the cases people actually want a prediction for are the slow ones.

Where it can still miss

The honest answer to “how good is it?” includes the seams. These are the ones worth knowing:

  • Coverage is marginal, not conditional. The 80% window is right ~80% of the time on average. A specific subject area or judge can run a few points over- or under-covered — the PIT edges above are exactly this, made visible.
  • A conformal recalibration is computed but currently held back. A split-conformal table can squeeze coverage a little tighter, but the fitter flagged a temporal data shift on the current corpus and gated it off, so the card ships the plain parametric quantiles you see scored above. The guard is deliberate: a questionable recalibration never silently ships. It re-enables itself automatically once the shift clears the gate.
  • The author isn’t known yet. A pending case has no signed opinion, but its author is almost always one of the three judges who heard it (or nobody, if it issues per curiam). So the estimate is a mixture: one component per panelist, each at that judge’s own tempo, plus a per-curiam component weighted by how often cases end that way. As weeks pass with no opinion, the mixture shifts toward the slower judges on the panel. The model can’t know who actually got the assignment, so a panel with one fast and one slow writer gets a wider window than one with three similar tempos.
  • Cold starts fall back to the prior. A brand-new judge or a never-before-seen subject contributes no specific signal; the model leans on the global tempo and the interval widens to say so.
  • Complexity proxies are still thin. Argument-audio length looked like a faithful duration proxy — the court’s archive serves constant-bitrate MP3s — but the file size is recorded for only a small fraction of cases, so the fitted slope was driven by a thin, unrepresentative sample and pulled a few long-recording cases implausibly early; it has been removed until coverage is high enough to trust. Brief page counts, the other strong external signal of a hard case, remain queued. Only three-judge panels are scored — en-banc volume is too thin to model.

One case the model refuses to predict

Occasionally a panel certifies a question of state law to a state high court and stays the appeal until that court answers. The wait is exogenous — the panel isn’t deliberating, it’s parked — and the survival model has no feature for it, so it would read the long pendency as ordinary slowness and emit a confidently-wrong date. These cases are therefore dropped from the model entirely (training rows and predictions alike), and the case page shows a plain annotation instead.

Once the state court answers, the stay lifts and a merits opinion usually follows within a few months. We surface a predicted window for that — measured from the answer date, re-centering as days pass like the main card — but it is deliberately a coarse fixed prior, not a fitted model: a log-logistic with a ~75-day median and an ~31–180-day 80% interval. There are fewer than three CA4 certified-question cases on record with both an answer date and a subsequent opinion, which is far too thin to fit anything — so the window is shown only to set rough expectations, and labeled as such on the case page. The prior ships as frontend/public/data/certified-question-prior.json, the single source of truth shared by the Python export and the TypeScript evaluator.

Run it yourself

Nothing here is a black box. The model, the backtest, and the exact artifacts the site reads are all in the repository (backend/app/services/release_estimator.py, release_calibration.py, release_features.py). Reproduce the scorecard with:

python -m app.cli.backtest_release --source json

The committed frontend/public/data/release-estimator.json, release-predictions.json, and release-estimator-calibration.json are the precise files the frontend reads at build time — and a faithful Python port of the frontend’s evaluator lives beside the fitter so the two never drift. Replace the artifacts, rebuild, and a different model is live. No server, no database, no infrastructure to roll out.

Questions or corrections? About this site.