# Posterior Desk > Paste the fit report a PyMC or NUTS run just gave you, and the `az.summary()` table beside it, and > find out whether the posterior can be reported. A free in-browser read does the arithmetic and the > coverage; three metered lanes do the judgement. Live at https://posterior-desk.skillsafe.ai/ · API tutorial at https://posterior-desk.skillsafe.ai/api.html ## What it is for The people who need this are the ones who have just watched a sampler finish and now have to decide whether to believe it. The failure mode is rarely a wrong model. It is a run whose diagnostics were skimmed: a divergence count read as a nuisance rather than as a statement about the part of the posterior the integrator could not reach; an R-hat compared against 1.1 because that is the number people remember, when the published threshold has been 1.01 since 2021; a `tau` quoted to three decimal places on 86 effective draws; a `shape=85` array of which the pasted summary carries two rows; a model comparison computed over chains that never mixed; and a sentence saying "the chains converged" sitting directly above a table that says otherwise. This app is a methods and reporting aid for the person who fitted the model. It cannot see your trace plots, it cannot see anything you did not paste, and it is not a substitute for a statistician. ## What the browser does for free, before any sign-in All of this runs on the page with no account and no credits. Nothing you paste leaves the browser until you deliberately run a metered lane. - **Coverage across 37 Bayesian-workflow reporting items**, in **three states** rather than two: `stated`, `none` (the report says there is none) and `missing` (never mentioned). The distinction matters in both directions, and the polarity is per item: `divergences: 0` is a complete answer and good news, while `prior predictive check: not performed` is a complete answer and a confirmed gap. - **The divergence rate.** Chains times draws per chain gives the post-warm-up total; the divergence count divides into it. 318 divergences in 4,000 draws is 7.95% and is graded blocking; one in 40,000 is not the same finding, and the severity follows the rate rather than the raw count. If either factor is missing the rate is reported as not derivable — never as zero. - **The warm-up ratio**, flagged when the sampler spent less time adapting than sampling. - **Every R-hat against 1.01 and 1.05**, the published working thresholds (Vehtari, Gelman, Simpson, Carpenter and Bürkner, 2021), not the older 1.1. - **Bulk ESS against both floors, as one finding rather than two.** Below 100 effective draws per chain the R-hat and ESS estimates are themselves unreliable — a precondition failure. Below 400 the posterior mean is not quotable to three significant figures. A parameter under both would otherwise produce two findings for one defect, so the binding threshold is named inside a single finding. - **Tail ESS**, which governs the credible-interval limits rather than the mean, reported separately because a parameter can have a trustworthy mean and untrustworthy limits. - **The Monte Carlo standard error against the posterior sd**, so how many digits you may print is computed rather than assumed. - **Interval arithmetic**: limits the wrong way round, a zero-width interval on a parameter that never moved, and a mean outside its own highest-density interval — the last graded `low`, because on a bimodal posterior that is correct and means only that the mean is a poor summary. - **Single-chain detection.** R-hat needs a between-chain variance. A value printed from one chain is not the diagnostic it looks like, and the page says so rather than reading it as a pass. - **Declared array shapes against the table's rows.** `shape=8` declares eight scalars. A summary carrying four of them has left four parameters unchecked, and every count on the page says so. - **Prose against numbers.** A report claiming all R-hat below 1.01 beside a table whose worst is 1.038; a warning about R-hat pasted beside a clean table (usually two different runs); an ESS claim the table's minimum contradicts; a model comparison over a posterior that has not converged; a PSIS-LOO number with no Pareto k behind it; an elpd difference with no standard error. - **The centred-hierarchy funnel**, the commonest cause of NUTS divergences and the one whose fix is a reparameterisation rather than a setting. - **Contradiction-aware negation.** A sentence that opens "there were no divergences" and ends with a count is reported as *contradictory*, not as clean. The count is then withheld rather than guessed, and every quantity that would have been derived from it is reported as not established. Two things it deliberately does **not** report as defects: an effective sample size larger than the number of draws (NUTS is antithetic on some posteriors and that is the expected result), and a posterior mean outside its own highest-density interval. ## The summary table it accepts The parser handles the layouts Python actually emits, including the two defaults that break naive readers: - `az.summary().to_string()` — the header carries one FEWER field than every data row, because the parameter index has no header. The name column count is derived from the data-row width rather than assumed. - `az.summary().to_csv()` — a LEADING EMPTY header cell instead. - markdown pipe tables, TSV, semicolon-delimited, and Stan-style `n_eff` / `Rhat` / `se_mean` / `2.5%` / `97.5%` columns. - Parameter names containing spaces, such as `beta[0, 1]`. The numeric columns are counted from the right using the header's own width and whatever is left over is rejoined, so a name a whitespace split tears in two survives intact. - Non-default credible levels: `hdi_5.5%` / `hdi_94.5%` reads as an 89% interval, and the lower of the pair becomes the lower limit regardless of column order. - `nan`, `inf`, `-inf`, blanks and `-` are each distinguished from zero. A blank ESS is not an ESS of nought. Column keys are matched by exact normalised token against an alias list, never by substring, because `mcse_mean` contains `mean`, `mcse_sd` contains `sd`, and `ess_bulk` and `ess_tail` both contain `ess`. ## Sampling, when the table is too long to send whole Every row is read and checked in the browser. If the table exceeds 120 rows, the rows sent to the model are chosen by a **golden-ratio (low-discrepancy) draw**, never by a fixed every-k-th stride. A posterior summary is the worst possible table to stride over: it is laid out as families of indexed elements, so a stride equal to a family's length returns element 0 of every family and nothing else. On top of the draw, at least one row from every parameter family survives, and so does the extreme row of every check — worst R-hat, lowest bulk ESS, lowest tail ESS, worst MCSE-to-sd ratio — because a sample that drops the one bad row hands the model a clean table while the page is flagging a problem. ## The three metered lanes One work object, one system prompt, one model (`gpt-terra`), an explicit `task` field, and one renderer per lane. - **`diagnose` — convergence and sampler health.** The sampler's configuration against what this model needs; whether the chains agree; where the failures concentrate and what shape that implies; the bulk and tail effective sample sizes; how many digits of each posterior mean survive the Monte Carlo error; and an explicit two-column scope of what this posterior may and may not support. - **`respec` — what to change before the next run.** The root cause with its evidence and a stated confidence; the model change written out before and after as real code; the prior and sampler changes with the current value taken from the input or marked "not stated"; a run plan ordered by cost, each step carrying the observation that means stop; and the next hypothesis if it does not work. This lane will not prescribe more draws for a convergence failure and will not prescribe thinning. - **`report` — the honest write-up.** A submittable methods paragraph; a diagnostics table where every value is labelled `computed`, `reported` or `not established`; per-claim wording with an explicit hedge flag; a limitations paragraph that names this fit's limitation rather than a generic one; and a reproducibility list. A fit that did not converge still gets a full write-up — of a run that did not converge, saying so. ## Lane isolation, and why the verdicts do not leak Every browser flag carries an area. A lane's verdict may be moved only by facts in the areas that lane owns; a binding fact from anywhere else surfaces as a **caveat** with its own panel, rather than silently changing the answer. | Lane | May move the verdict | May only caveat | | --- | --- | --- | | `diagnose` | sampler, convergence, efficiency, precision, geometry, interval, model | comparison, reporting | | `respec` | its own root-cause rule (no severity floor at all) | everything else | | `report` | reporting, comparison | the diagnostic areas | The verdict floors are a lookup table over `severity_by_area`, which the app computes and sends in the run input — so the rule in the prompt is a lookup against data rather than a sentence that can drift away from it. ## Accountability The browser's flags go into the run input, each with a `uid`, and the model must return exactly one `reconciliation` entry per uid: `confirmed`, `adjusted`, `set_aside`, `noted` (agreed, nothing to add) or `not_applicable`. The result panel prints one row per flag, so a flag the lane ignored is visible as **NOT ACCOUNTED FOR** rather than merely absent. A separate panel prints any place the lane's numbers disagree with the browser's arithmetic. ## Verdicts `sound`, `sound_with_caveats`, `revise`, `not_supported` — the same four words for the free read and for every lane, so a clean free read and a clean lane render the same colour and say the same word. ## Source Derived from one SkillSafe skill, credited: **@k-dense-ai/pymc** — Bayesian modelling with PyMC: hierarchical models, MCMC with NUTS, variational inference, LOO and WAIC comparison, and posterior checks. This app is a derived work, not a republication of that skill. ## Driving it from code Base URL `https://api.skillsafe.ai/v1/app-api`. Mint a token with `POST /guest` and `{"slug":"posterior-desk"}`; price a run with the free `POST /estimate`; run with `POST /run` or `POST /run-stream`. The run body IS the input object — it is never wrapped in an `input` key. The fields are `task`, `fit`, `table`, `goal`, `stage`, `notes` and `prescan_facts`. Full worked examples in eight languages, one per lane, at https://posterior-desk.skillsafe.ai/api.html