Add explicit per-plan gauge temperature offset
Test and deploy / test-and-deploy (push) Successful in 1m23s

This commit is contained in:
snowspeeder committed 2026-09-07 07:39:33 -04:00
1 parent d96ed22de1
commit eda3d8e77f
8 files changed
+90 -7

No files matched your search

+8 -5
View File
@@ -9,11 +9,14 @@ const numeric = (v) =>
? Number(v)
: null;
// No roaster selection, learned median, or override changes these illustrative points.
// An explicit per-plan gauge offset is a temperature difference: no +32 conversion.
// Legacy roaster selection, learned medians, and machine overrides remain ignored.
export const planTemperatureOffsetF = (plan) => numeric(plan?.temperatureOffsetF) ?? 0;
export function buildPlanCurve(plan, ledger) {
const t = plan.temps ?? {};
const fc = numeric(t.fc?.tempC) ?? 200;
const yellow = numeric(t.yellow?.tempC) ?? 160;
const offsetC = planTemperatureOffsetF(plan) / 1.8;
const fc = numeric(t.fc?.tempC) ?? (200 + offsetC);
const yellow = numeric(t.yellow?.tempC) ?? (160 + offsetC);
const maillardRate =
ledger.maillard > 0 ? Math.max(0, (fc - yellow) / ledger.maillard) : 0;
// Development's mean rise is lower than Maillard's, even for a short development.
@@ -23,13 +26,13 @@ export function buildPlanCurve(plan, ledger) {
key: "charge",
label: "Charge",
timeS: 0,
tempC: numeric(t.charge?.tempC) ?? 200,
tempC: numeric(t.charge?.tempC) ?? (200 + offsetC),
},
{
key: "tp",
label: "Turning point",
timeS: t.tp?.time ? parseDuration(t.tp.time) : 60,
tempC: numeric(t.tp?.tempC) ?? 95,
tempC: numeric(t.tp?.tempC) ?? (95 + offsetC),
},
{
key: "yellow",
+1
View File
@@ -19,6 +19,7 @@ export function blankPlan() {
return {
schemaVersion: 1,
sourceUrl: "",
temperatureOffsetF: "",
fields,
blendComponents: [
{ cultivar: "", group: "", process: "", sharePct: "", fcAnchor: "" },
+2
View File
@@ -1,6 +1,7 @@
import { computeLedger } from "./ledger.js?v=__ASSET_VERSION__";
import {
buildPlanCurve,
planTemperatureOffsetF,
pointsToPathD,
curveWarnings,
} from "./curve.js?v=__ASSET_VERSION__";
@@ -121,6 +122,7 @@ export function renderReportBody(plan, name = "") {
<h2>Coffee & roast intent</h2><p><b>Origin:</b> ${e(fields["1.3"] || "—")} · <b>Cultivar:</b> ${e(fields["1.1"] || "—")} · <b>Density:</b> ${e(fields["5.2"] || "—")} g/L</p><p><b>Weight-loss target:</b> ${e(fields["4.2"] || "—")}% · <b>Development:</b> ${time(l.C)} · <b>DTR:</b> ${l.checks.dtr.pct?.toFixed(1) ?? "—"}%</p>
<div class="callout">Use this as a starting hypothesis. Light, fruity coffee depends on the green coffee, roast progression and cup results. Machine selection never changes this plan. * Blank temperatures use an illustrative reference curve, not measured or calibrated targets.</div>
${warnings.length ? `<div class="callout">${warnings.map((w) => `<p>${e(w)}</p>`).join("")}</div>` : ""}
${planTemperatureOffsetF(plan) ? `<p><b>Gauge temperature offset:</b> ${planTemperatureOffsetF(plan) > 0 ? "+" : ""}${e(planTemperatureOffsetF(plan))}°F. Applied to illustrative temperature references; drop follows adjusted milestones. Entered targets, preheat and actual readings are unchanged.</p>` : ""}
<h2>Milestone plan</h2><table><thead><tr><th>Milestone</th><th>Time</th><th>BT (°F)</th><th>What to look for</th></tr></thead><tbody>${rows}</tbody></table>
<h2>Roast curve</h2><div class="chart">${renderReportCurve(p, l)}</div><p>The smooth line interpolates milestones; it is not a predicted sensor trace. Aim for controlled progression after recovery. Phase averages cannot rule out a brief crash or flick.</p><div class="footer">Roast plan · Grams & Fahrenheit</div></section>
<section class="sheet"><div class="record-intro"><h1>Roast schedule & record</h1><div class="subtitle">${e(name || fields["0.1"] || "Roast plan")} · Use at the roaster</div>