Add brewing section, plan chat, roaster learning, API tokens, Swagger docs, and full backup
Test and deploy / test-and-deploy (push) Successful in 1m6s
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:
co-authored by
Claude Fable 5
parent
5efaeb63c9
commit
38c7d01e03
+14
-9
@@ -19,27 +19,32 @@ export function rorToY(rorCPerMin) {
|
||||
return PLOT.y1 - rorCPerMin * 9;
|
||||
}
|
||||
|
||||
/** Build the five plan-curve points from the ledger + box-8 temps, falling back to machine medians. */
|
||||
export function buildPlanCurve(plan, ledger) {
|
||||
/** Build the five plan-curve points from the ledger + box-8 temps. Blank temps fall back to
|
||||
* the user's own learned roaster behavior (median milestone temps from their uploaded .alogs,
|
||||
* via /api/roaster-profile) when available, else the reference machine medians. */
|
||||
export function buildPlanCurve(plan, ledger, roasterProfile = null) {
|
||||
const temps = plan.temps ?? {};
|
||||
const num = (v) => {
|
||||
const n = Number.parseFloat(v);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
};
|
||||
const learned = roasterProfile?.n > 0 ? roasterProfile.medians : {};
|
||||
const fallback = (learnedValue, referenceValue) =>
|
||||
Number.isFinite(learnedValue) ? learnedValue : referenceValue;
|
||||
|
||||
const points = [
|
||||
{ key: "charge", label: "Charge", timeS: 0, tempC: num(temps.charge?.tempC) ?? MACHINE.charge.medianC },
|
||||
{ key: "tp", label: "Turning point", timeS: parseTpTime(temps.tp?.time), tempC: num(temps.tp?.tempC) ?? MACHINE.turningPoint.medianC },
|
||||
{ key: "yellow", label: "Yellow", timeS: ledger.yellow, tempC: num(temps.yellow?.tempC) ?? MACHINE.yellow.medianC },
|
||||
{ key: "fc", label: "First crack", timeS: ledger.A, tempC: num(temps.fc?.tempC) ?? MACHINE.firstCrack.medianC },
|
||||
{ key: "drop", label: "Drop", timeS: ledger.D, tempC: num(temps.drop?.tempC) ?? MACHINE.drop.medianC },
|
||||
{ key: "charge", label: "Charge", timeS: 0, tempC: num(temps.charge?.tempC) ?? fallback(learned.chargeTempC, MACHINE.charge.medianC) },
|
||||
{ key: "tp", label: "Turning point", timeS: parseTpTime(temps.tp?.time, learned.turningPointS), tempC: num(temps.tp?.tempC) ?? fallback(learned.turningPointTempC, MACHINE.turningPoint.medianC) },
|
||||
{ key: "yellow", label: "Yellow", timeS: ledger.yellow, tempC: num(temps.yellow?.tempC) ?? fallback(learned.yellowTempC, MACHINE.yellow.medianC) },
|
||||
{ key: "fc", label: "First crack", timeS: ledger.A, tempC: num(temps.fc?.tempC) ?? fallback(learned.firstCrackTempC, MACHINE.firstCrack.medianC) },
|
||||
{ key: "drop", label: "Drop", timeS: ledger.D, tempC: num(temps.drop?.tempC) ?? fallback(learned.dropTempC, MACHINE.drop.medianC) },
|
||||
];
|
||||
|
||||
return points.filter((p) => p.timeS !== null && p.timeS !== undefined);
|
||||
}
|
||||
|
||||
function parseTpTime(str) {
|
||||
if (!str) return 52; // MACHINE.turningPoint.medianTime, "0:52"
|
||||
function parseTpTime(str, learnedS = null) {
|
||||
if (!str) return Number.isFinite(learnedS) ? learnedS : 52; // 52 = MACHINE.turningPoint.medianTime "0:52"
|
||||
const m = String(str).match(/^(\d+):(\d{1,2})$/);
|
||||
if (!m) return null;
|
||||
return Number(m[1]) * 60 + Number(m[2]);
|
||||
|
||||
Reference in New Issue
Block a user