Add brewing section, plan chat, roaster learning, API tokens, Swagger docs, and full backup
Test and deploy / test-and-deploy (push) Successful in 1m6s

Brewing:
- roasted_beans + brews tables; /beans (bean management with LLM URL
  prefill) and /brews (silhouette brewer picker across immersion/
  percolation/espresso, recipe fields, auto ratio, 0-10 rating, tasting
  notes); bean remaining weight derived from logged brew doses
- Green inventory lot form also prefills from a product URL

Navigation/UX:
- Side nav is now generated from one definition in nav.js, grouped
  Roasting / Brewing / account, consistent on every page

LLM:
- 'Ask the LLM' chat drawer on the planner (stateless /api/plan-chat)
  grounded in the plan, computed ledger, learned pace, and a new
  roaster-behavior profile aggregated from uploaded .alogs
  (/api/roaster-profile: TP lag, phase RoR, median milestone temps)
- The profile also feeds roast reviews and the planner curve's fallback
  milestone temps

API platform:
- User-generated bearer tokens (rpt_…) with account-page management;
  token requests skip CSRF; hand-authored OpenAPI 3 spec at
  /api/openapi.json rendered by self-hosted Swagger UI at /api-docs
- Full-database backup export/import (admin) + per-user data export

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Shane Maynard
2026-08-08 22:37:42 -04:00
co-authored by Claude Fable 5
parent 5efaeb63c9
commit 38c7d01e03
33 changed files with 3498 additions and 205 deletions
+8 -3
View File
@@ -59,7 +59,7 @@ function rorSegments(curve) {
/** Deterministic, model-free digest of the parsed roast (+ optional plan targets). Also stored
* alongside the model's text so the UI can show the same numbers the model was judged on. */
export function buildRoastFacts(parsed, plan) {
export function buildRoastFacts(parsed, plan, roasterProfile = null) {
const curve = parsed.curve ?? [];
const milestone = (key) => parsed.milestones?.find((m) => m.key === key) ?? null;
const yellow = milestone("yellow");
@@ -94,6 +94,11 @@ export function buildRoastFacts(parsed, plan) {
rorSegments: rorSegments(curve),
parserWarnings: parsed.warnings ?? [],
planTargets: null,
// How this user's machine typically behaves, learned from their previously uploaded
// roasts — lets the review distinguish "your roaster always lags like this" from a
// one-off anomaly.
typicalRoasterBehavior:
roasterProfile && roasterProfile.n > 0 ? roasterProfile : null,
};
if (plan) {
@@ -122,12 +127,12 @@ export function buildRoastFacts(parsed, plan) {
* @param {string|null} preferredModel admin-configured "provider:id", empty/null for auto
* @returns {Promise<object>} evaluation object (schema above + `facts`)
*/
export async function evaluateRoast(parsed, plan = null, preferredModel = null) {
export async function evaluateRoast(parsed, plan = null, preferredModel = null, roasterProfile = null) {
const modelRuntime = await getModelRuntime();
const model = await pickModel(preferredModel);
if (!model) throw noModelError();
const facts = buildRoastFacts(parsed, plan);
const facts = buildRoastFacts(parsed, plan, roasterProfile);
const resourceLoader = new DefaultResourceLoader({
cwd: process.cwd(),