Files
roast_command_center/README.md
T
snowspeeder 824a964f9d
Test and deploy / test-and-deploy (push) Successful in 2m28s
Rebuild printable roast plans and remove machine-based adjustments
2026-09-05 14:56:11 -04:00

134 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Roast Planner Webapp
A fillable, live-computing web version of the manual coffee roast plan worksheet
(`hope_roaster/roast-planner/manual-roast-planner.html`), plus:
- **Prefill from a bean URL** — paste a roaster's product page URL; the server fetches it and
uses the Pi Coding Agent SDK (zero-tool, one-turn extraction) to pull out whatever it can
find (cultivar, origin, processing, roast level), then deterministically maps that to
worksheet field IDs using the same reference tables as the paper worksheet. It never
invents a first-crack anchor or a modifier — those only ever come from the reference
tables, keyed off what the page actually states.
- **Reference curve from an Artisan `.alog`** — upload a real roast log (or browse an administrator-only local
library directory) to overlay its actual milestones/curve on the same SVG grid as your
plan, dashed and in a different color.
- **Finished-roast log with Pi agent review** — upload one or more finished Artisan `.alog`
files against a plan (or standalone from `/roasts`); each is stored verbatim (downloadable
as a backup), parsed, and deep-reviewed asynchronously by the same zero-tool Pi agent
pattern as prefill (grade, highlights, concerns, next-batch suggestions, plan-vs-actual).
The `/roasts` page lists every actual roast and renders its curve with the plan overlaid.
- **Brewing section** — the app is split into Roasting and Brewing workflows in the nav.
`/beans` manages roasted/purchased coffee (with LLM URL prefill from a roaster's product
page); `/brews` logs every cup against a bean with a silhouette brewer picker (immersion /
percolation / espresso & pressure — 13 methods), recipe fields (dose, water/yield, auto
ratio, grind, temp, times), a 0–10 rating, and tasting notes. Bean remaining weight is
derived from logged brew doses. Green inventory's lot form also prefills from a URL.
- **Plan chat and roast history** — plan chat uses the open plan and computed ledger.
Roasters organize uploads and show descriptive statistics; roaster selection, learned pace,
and historical temperature medians never modify plan timing or temperature suggestions.
- **API + tokens + Swagger** — every capability (including admin) is a JSON endpoint,
documented by a hand-authored OpenAPI 3 spec at `/api/openapi.json` and a self-hosted
Swagger UI at `/api-docs`. Users generate bearer tokens (`rpt_…`) on the Account page;
token requests skip CSRF (header-borne credentials can't be forged cross-site).
- **Backup** — Admin → Backup exports the entire database as one JSON file and can import
it back transactionally (delete-and-restore, refuses backups with no active admin, keeps
the importing admin's session when possible). Users can download their own data from the
Account page.
- **Academy** — `/academy` teaches roasting and brewing as narrated, animated slide lessons:
green-bean physics through every roast stage (drying, Maillard, first crack, development,
curve reading, defects) and extraction plus technique for every brewer family. Narration is
Azure TTS (`en-US-Andrew` HD voice), pre-generated to committed MP3s by
`scripts/generate-academy-audio.mjs` (reads `AZURE_VOICE_KEY`/`AZURE_VOICE` from `.env`,
region eastus — run it locally after editing lesson scripts; production never needs the
key). Slides carry display transcripts as captions and phonetic respellings for the voice.
- **Live ledger** — shared timing math with explicit manual corrections, invalid-duration
checks, density-unit warnings, and a direct development override. Light-roast modifier
suggestions have a disclosed 1:15 floor; an explicit override takes precedence.
- **Printable report** — preview, print, or download a standalone HTML file in grams and
Fahrenheit. Includes an annotated smooth SVG curve, phase durations, milestone cues,
separate preheat fields, actuator schedule, and roast/cup record. Blank temperature inputs
use an illustrative reference (not a calibrated prediction); entered readings are preserved.
Local Node development requires Node 22.19 or newer (Docker uses Node 24).
Product-page fetching accepts public HTTPS destinations on port 443, validates every DNS
answer and redirect, pins the socket address, and keeps a deadline through body streaming.
The former Browserless fallback is removed because it could bypass these network protections.
## Run it
```bash
cp .env.example .env # set strong secrets
npm install
npm test
# local PostgreSQL stack (database is not published to the host)
docker compose --env-file .env up --build
```
The public landing page is at `/`; plans require an account at `/app`. Production is configured for `https://roast.srmr.xyz`: retain `APP_ORIGIN=https://roast.srmr.xyz` and `COOKIE_SECURE=true` behind its HTTPS proxy. Database migrations in `db/migrations/` run at application startup exactly once.
### First administrator
Generate `BOOTSTRAP_SETUP_TOKEN` with `openssl rand -base64 48`, keep it only in the deployment environment, then call `POST /api/auth/bootstrap` with that token, `[email protected]`, and a 12+ character password. The endpoint can create that account only once. Remove the setup token after success; it is optional thereafter and no administrator password is stored in source control.
### Pi agent configuration in Docker
The `app` service mounts `PI_AGENT_CONFIG_DIR` (default `./appdata/pi-agent`) read-only at `/run/pi-agent-config`. Its entrypoint copies that seed into the non-root Node user's writable runtime configuration directory before startup: Pi's credential storage needs to create a lock beside `auth.json`. This lets `/api/prefill` use the configured model without baking credentials into the image or mutating the host configuration. The directory is ignored by Git and Docker build context; do not commit its contents.
Before bringing up the stack, sync only the local Pi agent configuration you intend to make available to the container:
```bash
mkdir -p appdata/pi-agent
rsync -a --delete ~/.pi/agent/ appdata/pi-agent/
docker compose --env-file .env up --build
```
Set `PI_AGENT_CONFIG_DIR` to another protected host directory instead if preferred. Restrict access to that directory because it can contain provider credentials. The mount is read-only, so Pi cannot alter the host configuration. If deployed behind a reverse proxy, set `TRUST_PROXY` only to that proxy's specific IP/CIDR (or keep it blank when the app is directly exposed).
## Mobile and PWA use
The planner is responsive and caches its app shell for offline use after the first visit. Browser installation and service-worker caching require HTTPS in production (localhost is exempt). Put the Docker container behind an HTTPS reverse proxy before using it as an installable PWA on a phone.
Prefill requires a model configured via the `pi` CLI (`~/.pi/agent/{models,auth}.json`) —
without one, `/api/prefill` returns `503 no_model` rather than crashing. Everything else
(the form, ledger, curve, `.alog` upload) works with no model configured. Which configured
model runs prefill and roast reviews is chosen in the admin page's **LLM** section
(`app_settings.llm_model`, "Auto" = first available); the UI refers to the model backend
simply as "the LLM".
## Layout
- `shared/` — browser-safe ESM modules imported by *both* the Node server and the browser
(served statically at `/shared/`). This is the single source of truth for the ledger math,
time parsing, and reference data tables — never duplicate this logic in `server/` or `public/js/`.
- `server/` — Express routes: `/api/prefill`, `/api/alog`, `/api/alog/library[/:filename]`.
- `public/` — the static frontend: `index.html` (planning form), `app.css` (screen UI), and `js/`.
`shared/report.js` owns the standalone/print report. `worksheet.css` is a legacy asset.
## Where the reference numbers come from
`shared/reference-data.js` is ported from
`/Users/shane/dev/hope_roaster/roast-planner/manual-roast-planner.html`'s Step 1 (cultivars),
Step 3 (processing), Step 4 (roast levels), Step 8 (machine bands), and Step 7 (sanity bands).
If those tables change in the paper worksheet, port the change here too.
## Known gaps (v1)
- Offline drafts are intentionally scoped to the authenticated browser account and are cleared on logout; account-backed plans remain the authoritative copy.
- Roastetta (roastetta.com) integration is intentionally out of scope — it needs a headed,
Cloudflare-clearing browser and the operator's own credentials. Use the `.alog` file picker,
or point `ALOG_DIR` at wherever the `roastetta` skill already downloaded files.
## Regression checks
`npm test` includes account ownership, authorization, persistence, parsing, planner/report,
URL-fetch security, and Docker startup/migration checks. For the Chromium interaction check:
```sh
npx playwright install chromium
npm run test:browser
```
The browser check covers Fahrenheit/Celsius round trips without deleting inputs, the report
preview, standalone download, and print-only layout. It runs separately from the default test
suite so production CI does not require installing a browser.