Skip to content

Repository files navigation

Blend Stability Calculator

How often does a single espresso dose actually respect your blend recipe?

A 60/40 blend is a statement about a whole bag. Nobody drinks a bag. The grinder takes about 112 beans, and 112 whole objects cannot be split 60/40 on demand.

This tool computes — exactly, not by simulation — the probability that every origin in a blend lands within tolerance in the same dose.

JOINT STABILITY          37.28%
  Probability that every origin lands within tolerance in the
  same dose. This is the figure that answers the question.

That is a 60/40 blend, 18 g dose, ±5% tolerance, 1 kg bag. Roughly two shots in five hold the recipe. Across 40 real draws, Brazil ranged from 55 to 77 beans — 49% to 69% of the cup, from a recipe that says 60%.


Install

There is nothing to install. The statistics use only the Python standard library, because math.comb gives exact arbitrary-precision integers — the same calculation in floating point overflows and silently returns wrong answers.

git clone https://github.com/Rhadue/blend-stability.git
cd blend-stability
python3 blend_stability.py

Python 3.9 or newer. numpy is optional and only accelerates Monte Carlo; a pure-Python sampler ships as a fallback.

Use it

python3 blend_stability.py --bag 1000 --dose 18 --tolerance 5 \
  --origin "Brazil Santos:60" --origin "Colombia Supremo:40"

Compare every method on the same inputs:

python3 blend_stability.py --compare

Or run the web interface — a local page, no dependencies, nothing leaves your machine:

python3 blend_ui.py
Option Meaning
--bag, --dose grams; bag size sets the population N
--origin "Name:pct" repeatable, must total 100
--tolerance percent, relative to each share by default
--absolute read tolerance as percentage points instead
--method auto (default), exact, multinomial, normal, montecarlo
--compare run every applicable method side by side
--self-test verify the engine against reference values

Joint, not the product of marginals

This is the mistake worth naming, because almost every hand-rolled blend calculator makes it.

The marginal probability is "this one origin is within tolerance". The joint probability is "every origin is within tolerance, in the same dose". The second is what matters, and it is not the product of the first.

Origin counts must sum to the dose, so they are negatively correlated — never independent. If Brazil is over-represented, Colombia is under-represented by exactly the same amount. Multiplying the marginals assumes two independent coin flips when you actually have two ends of one seesaw.

For the default blend the difference is close to a factor of two:

Joint probability (correct) 38.25%
Product of marginals (wrong) 19.73%

The tool reports both, and labels which is which.

Methods

The four methods are not competing alternatives. They are one exact model and its documented approximations:

multivariate hypergeometric      exact — the ground truth
          |  N -> infinity
          v
    multinomial                  valid when the dose is small vs the bag
          |  counts large
          v
  normal approximation           fastest; marginals only

    Monte Carlo                  when exact enumeration is intractable

--method auto picks on two independent criteria:

  • Which method is needed — exact lattice enumeration when the acceptance region is tractable, Monte Carlo otherwise.
  • Whether bag size matters — the sampling fraction n/N against the classic 10% rule. Below it, the multinomial is indistinguishable from exact; above it, the finite population genuinely changes the answer.

The normal method deliberately returns no joint probability. Obtaining one would require the multivariate normal CDF over a box; multiplying marginals instead is simply wrong, and the tool will not do it quietly.

Correctness

python3 blend_stability.py --self-test

The engine is checked against values derived independently, and against its own limiting behaviour: the exact multivariate hypergeometric converges to the binomial as the bag grows, which is two separate code paths agreeing.

  • Exact methods are exact — lattice enumeration, no sampling error.
  • Monte Carlo results carry Wilson confidence intervals. At 10,000 draws the standard error is roughly ±0.5 percentage points, so a result quoted to two decimals is false precision. The tool reports the interval instead.
  • Bean counts per origin use largest-remainder apportionment, so they always sum to exactly N.
  • Impossible tolerance windows are detected and explained rather than silently reported as 0%. A 5% origin in a 112-bean dose at ±5% relative tolerance has an acceptance window of 5.32 to 5.88 beans — no whole number fits, so the result is 0% by construction whatever the method.

What this does and does not establish

It establishes that composition varies from dose to dose, by amounts that grow sharply with blend complexity. This is arithmetic, not observation.

Origins (equal shares) ±5% ±10% ±20%
2 36.63% 70.58% 97.21%
3 8.60% 30.21% 71.41%
5 0.11% 1.57% 22.88%
7 0.00% 0.09% 7.39%

The collapse holds at every tolerance, so the conclusion does not depend on where you draw the line.

It does not establish that you can taste it. Nothing here measures perception, and no amount of arithmetic can. A compositional swing is not automatically a sensory one.

The model also assumes a perfectly mixed bag, which is the most favourable case possible. Real hoppers stratify by bean size under vibration, so these numbers are a floor, not a ceiling.

Not a substitute for sensory QC. This computes composition, not cup quality. Do not use it as the sole basis for purchasing, blending, or pricing decisions. See the warranty and liability sections of the licence.

Documentation

docs/ contains the written argument with its figures, as a four-page PDF.

Colophon

The statistical engine, the interface and the written argument were built in collaboration with Claude Code (Anthropic). So was the audit that started the project: three earlier calculators were found to disagree with each other on identical inputs, and the reasons — multiplied marginals, an arbitrary population size, per-origin rates that were mathematically identical to each other — are recorded in CHANGELOG.md.

Nothing here rests on that collaboration being trusted. Every figure was cross-checked against an independent implementation, the exact and Monte Carlo paths agree to within their confidence intervals, and the whole engine is reproducible with --self-test. Check the numbers rather than the provenance.

Claude Code is credited in the commit history through Assisted-by: trailers, following the convention the Linux kernel adopted for AI-assisted contributions. It is deliberately not Co-authored-by:, and not an author entry in CITATION.cff: authorship carries accountability for the analysis, and that rests with the named author alone.

Licence

Code is licensed under the Apache Licence 2.0 — see LICENSE and NOTICE.

Apache rather than MIT for a specific reason: section 4(b) requires modified files to be marked. The whole value of this project is that the numbers are verifiable. An altered statistical model still presenting itself as this tool would undermine that for everyone using it.

Documentation in docs/ is licensed under CC BY 4.0.

Citation

GitHub renders a Cite this repository button from CITATION.cff. In prose:

Mânea, Radu Ioan (2026). Blend Stability Calculator — exact probability that a single espresso dose respects its blend recipe. https://github.com/Rhadue/blend-stability

Built at Papa Jacques roastery.

About

A tool for designing coffee blends. What's the exact probability that a single espresso dose respects its blend recipe? Now you know.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages