feat(analysis): attenuation, fermentation curves and brew timings #12

Merged
hermes merged 4 commits from feat/deep-analysis into master 2026-11-09 14:18:45 +10:00
2 changed files with 63 additions and 4 deletions
Showing only changes of commit 4d1e070d74 - Show all commits
+22 -1
View File
@@ -42,6 +42,7 @@ The tests use `mongomock`, so they need no database and no network.
| `/brews` | The brew process: record a reading, review every run |
| `/active` | What is in the fermenter right now — tap, gravity, projected strength |
| `/history` | How every brew has gone, with charts and the standard-vs-legacy ABV comparison |
| `/analysis` | How consistently the brews ferment — attenuation, fermentation curves, brew timings |
### `/active` — the active beers page
@@ -59,6 +60,27 @@ A run with only one reading and no recorded original gravity has no reliable
estimate, so the alcohol figure shows a dash rather than a misleading number.
Record the OG on the run (or take a second reading) and it appears.
### `/analysis` — how the brews ferment
The page that turns the history into something you can brew better from:
- **Attenuation by brew** — what fraction of the original gravity was
fermented away, for every finished brew.
- **Fermentation curves** — the typical shape of a fermentation, drawn as a
median curve with the min–max envelope behind it. Plotting every brew
individually gives sixteen near-identical lines and an unreadable legend.
- **Every finished brew** — OG, FG, ABV, attenuation, days in the fermenter,
days to reach 1.010, and the finish date.
- **Readings that need a second look** — readings outside the plausible
hydrometer range.
- Headline figures: average attenuation and its spread, average brew time,
average time to 1.010.
A reading that is physically impossible — a final gravity below the original
gravity by more than the wort itself could have — is excluded from the
averages rather than silently distorting them, and the run is listed on the
page so it can be corrected.
### Run metadata
Some details cannot be derived from the readings, so they are stored per run in
@@ -94,7 +116,6 @@ src/flask/
views/ blueprints: auth, brews, analysis
mongo/repository.py Mongo access for the beer_data and beer_runs collections
templates/ Jinja templates
static/ CSS, JS, fonts, logo (all served locally — no CDN)
static/ CSS, JS, fonts, logo (all served locally - no CDN)
tests/ pytest suite
scripts/ operational scripts (the run-metadata backfill)
+41 -3
View File
@@ -69,15 +69,53 @@ env:
## Deploying
Pushing to `master` triggers `.gitea/workflows/build_push.yml`, which runs the
test suite and then builds and pushes the image. **The workflow does not
currently deploy to the cluster** — apply the manifests yourself:
Pushing to `master` triggers `.gitea/workflows/build_push.yml`, which builds
the image, pushes it, **deploys it to the cluster and verifies it**. There is
no manual step.
The workflow is a single job, in this order:
1. **Checkout**
2. **Create Kubeconfig** — writes `secrets.KUBEC_CONFIG_BUILDX_NEW_2` to
`$HOME/.kube/config`. This is FIRST because the buildx `kubernetes` driver
needs it to create its builder pod in the cluster.
3. **Set up Docker Buildx** — `driver: kubernetes`, `namespace=gitea-runner`.
4. **Login to the registry** — `secrets.REG_PASSWORD`.
5. **Build and Push** — `linux/amd64` only. A multi-arch build runs qemu,
which crashes the buildkit pod on this NUC (exit 255, `SandboxChanged`)
and the build then hangs for many minutes.
6. **Deploy** — installs kubectl, applies `kube/*.yaml`, creates the
image-pull secret idempotently, then `rollout restart` + `rollout status`.
The rollout restart is required: the image tag in the manifest is
`:latest`, so `kubectl apply` alone does not roll the pods.
7. **Verify the deployed app** — exercises `/healthz` and `/readyz` from
inside the pod, using the pod's own python (there is no `curl` in the
slim image).
> The deploy step does **not** delete the namespace. That would destroy the
> hand-provisioned `beer-db` and `beer-app` Secrets.
### Deploying by hand
If you need to deploy without going through CI — for example to test a
manifest change — do the same thing the workflow does:
```bash
kubectl apply -f kube/beer_namespace.yaml
kubectl apply -f kube/beer-deployment.yaml
kubectl apply -f kube/beer-service.yaml
kubectl -n beer rollout restart deployment/beer-deployment
kubectl -n beer rollout status deployment/beer-deployment --timeout=180s
```
Validate the manifests before applying them. `kubectl apply` **silently
rejects** a manifest with a schema error, so a broken manifest leaves the
old Deployment in place while the job still reports success:
```bash
kubectl apply --dry-run=client -f kube/
```
### Verify the deploy actually took
CI green and pods `Ready` do **not** prove the new code is running. Check the