Precision calculators, batch tracking and production-sheet export for micro-batch fermentation.
No install and no account. The whole app runs inside your browser (Python compiled to WebAssembly), so nothing you type ever leaves your device. The first load takes 10–30 seconds while Python starts; later visits are cached.
Home fermenters of wine, cider and kombucha keep redoing the same fiddly arithmetic (temperature-correcting a hydrometer reading, working out how much sugar to add, judging whether a ferment has stalled). FermentOps puts all of it in one place.
| Tab | What you get |
|---|---|
| Precision Calculators | Hydrometer temperature correction · sugar-boost amounts · ABV and attenuation · dehydrator wet-to-dry yield, with a Metric/Imperial toggle |
| Active Batch Tracker | Every ferment on one dashboard: stage, timeline, airlock bubble-rate trend, gravity, ABV. Log readings, record hydrometer checks, keep an activity log |
| Production Log Exporter | Compile any batches into a clean Markdown production sheet to copy or download |
The tracker also raises alerts for the things that actually go wrong: temperature outside the stage window, a silent airlock while gravity is still high (a possible stuck ferment), no reading in 24 hours, and batches running past their expected duration.
Requires Python 3.11 or newer.
git clone https://github.com/python-matte/fermentops.git
cd fermentops
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
streamlit run app.pyRun locally, your batches are saved to a SQLite file at data/fermentops.db. That file is git-ignored, so your real data is never committed. Set FERMENTOPS_DB=/path/to/file.db to store it elsewhere.
| Mode | How | Where data lives |
|---|---|---|
| Persistent (default) | streamlit run app.py |
SQLite file on your disk |
| Demo | FERMENTOPS_DEMO=1 streamlit run app.py |
Nowhere. Each browser tab gets a private, freshly seeded sandbox that resets on refresh |
| Browser (the live site) | Automatic | Nowhere. Same as demo mode |
On first run the four demo batches are seeded once. Delete them from the tracker's Manage batch panel.
flowchart LR
UI[app.py<br/>Streamlit UI] -->|inputs| CL[core_logic.py<br/>pure functions]
UI -->|validated models| SC[schemas.py<br/>Pydantic]
CL -->|reads| SC
CL -->|results, alerts, Markdown| UI
UI -->|save / load| ST[storage.py<br/>SQLite or no-op store]
The design goal is that the part that has to be right is easy to prove right:
core_logic.pyholds every formula and alert rule as side-effect-free functions. They never read the clock (callers passnow) and never touch the disk, so each one is trivially testable. Bad input raises a specificFermentValueErrorinstead of producing a plausible wrong number.schemas.pydefines the data model with Pydantic, so invalid state (a gravity of 5.0, a target above the starting gravity) cannot be constructed.storage.pyhides persistence behind one small interface with two implementations, which is what lets the same code run on a laptop and in a browser tab.app.pyis only the UI. It contains no formulas.
Full detail is in docs/ARCHITECTURE.md, and the maths, with worked examples and limitations, is in docs/FORMULAS.md.
pip install -r requirements-dev.txt
ruff check .
pytest130+ tests cover:
- The maths — worked examples checked by hand, boundary values, and the published hydrometer polynomial.
- Defensive validation — every rejected input (a starting SG of exactly 1.000, FG above OG, moisture reduction above moisture content, zero trays, NaN and infinity).
- Alert rules — including the boundaries (inclusive temperature windows, the two-day grace period before a ferment can count as "stuck").
- Persistence — round trips, ordering, in-place updates, and the demo/browser stores.
- The UI itself — Streamlit's headless
AppTestdrives the real app: calculators, unit toggle, logging a reading, creating and deleting a batch, exporting a sheet. - The deploy — a guard that fails if the app imports a module the web build doesn't ship.
CI runs lint and tests on Python 3.11 and 3.13 for every push and pull request, and the deploy workflow refuses to publish if either fails.
GitHub Pages serves only static files, and Streamlit normally needs a Python server. This project bridges that gap with stlite, a build of Streamlit that runs in the browser on WebAssembly (Pyodide). The deployed site runs the same unmodified source files as the local app; scripts/build_site.py copies them next to a small host page, and a GitHub Actions workflow publishes the result. See docs/DEPLOYMENT.md for how it works, how to reproduce it, and the trade-offs.
This project is an example of using an AI coding assistant on a personal, real-world problem rather than a work task. docs/BUILT_WITH_AI.md describes the working method: how the code is structured so AI-written work can be checked, and how the tests keep the result honest.
FermentOps is a hobbyist calculator, not laboratory software. The sugar and ABV formulas are standard approximations (see docs/FORMULAS.md); your hydrometer, your sanitation and your own judgement matter more than any number on screen. Nothing here is food-safety advice.

