hvac-optimization / physics.js
subhan07's picture
Match HVAC AI Control Center: all O1-O20 studios, Operations and OEH guide
802f0fa verified
Raw History Blame Contribute Delete
13.4 kB
/** OEH teaching curves — same model as backend/services/oeh_guide_physics.py. Always SIMULATED. */
(function (g) {
const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
const avg = (arr) => (arr.length ? arr.reduce((s, n) => s + n, 0) / arr.length : 0);
const oat = (hour, mean, amp) => mean + amp * Math.cos((hour - 15) * (Math.PI / 12));
const loadCurve = (hour) => clamp(Math.sin(((hour - 6) / 14) * Math.PI), 0.05, 1);
const occupancyCurve = (hour) => clamp(Math.sin(((hour - 8) / 10) * Math.PI), 0.03, 1);
const f = (v, key, d) => {
const n = Number(v[key]);
return Number.isFinite(n) ? n : d;
};
const pct = (n) => `${n.toFixed(0)}%`;
function pointAt(oid, x, v) {
if (oid === "O1") {
const occ = f(v, "occStart", 8.5);
const sev = f(v, "severity", 50);
const baseStart = occ - 2.5, baseStop = 17.5;
const optStart = occ - (0.5 + (sev / 100) * 2);
const optStop = 17.5 - (0.1 + ((100 - sev) / 100) * 0.4);
return [baseStart <= x && x < baseStop ? 100 : 0, optStart <= x && x < optStop ? 100 : 0];
}
if (oid === "O2") {
const db = f(v, "deadBand", 1);
const pb = f(v, "propBand", 1);
const dev = clamp(3 * Math.sin(((x - 9) / 14) * Math.PI), -3, 3);
const out = (d, band, p) => {
const half = band / 2;
if (Math.abs(d) <= half) return 0;
return clamp(((Math.abs(d) - half) / Math.max(p, 0.01)) * 100, 0, 100);
};
return [out(dev, 1, 1), out(dev, db, pb)];
}
if (oid === "O3") {
const phases = [0, 1.2, 2.4, 3.6, 4.8];
const devs = phases.map((ph) => Math.max(0, 2.5 * Math.sin(((x - 8 + ph) / 12) * Math.PI)));
devs[4] += f(v, "faultBias", 1.5);
const baseline = Math.max(...devs);
const n = Math.min(Math.trunc(f(v, "numAvg", 3)), 5);
const sorted = [...devs].sort((a, b) => b - a);
const optimized = sorted.slice(0, n).reduce((s, n) => s + n, 0) / n;
return [baseline, optimized];
}
if (oid === "O4") {
const load = (f(v, "peakLoad", 75) / 100) * loadCurve(x);
const delay = f(v, "stageDelay", 10);
const baseline = clamp(Math.ceil(load * 3 + 0.45), 0, 3);
const optimized = clamp(Math.ceil(load * 3 - ((delay - 5) / 15) * 0.3), 0, 3);
return [baseline, optimized];
}
if (oid === "O5") {
const demand = (f(v, "demandAmp", 80) / 100) * loadCurve(x);
const openNeeded = 40 + demand * 60;
const optSpeed = clamp(100 * (openNeeded / Math.max(f(v, "targetOpen", 92), 1)), 25, 100);
return [100, (optSpeed / 100) ** 3 * 100];
}
if (oid === "O6") {
const demand = (f(v, "heatSeverity", 60) / 100) * (1 - loadCurve(x));
const floor = f(v, "boilerType", 1) ? 40 : 55;
return [82, floor + demand * (82 - floor)];
}
if (oid === "O7") {
const load = (f(v, "loadSeverity", 80) / 100) * loadCurve(x);
return [6.5, 6.5 + (1 - load) * 5.5];
}
if (oid === "O8") {
const wb = oat(x, f(v, "wetBulbMean", 20), 4);
return [f(v, "wetBulbMean", 20) + 11.5, wb + f(v, "approach", 3.5)];
}
if (oid === "O9") {
const load = 100 * loadCurve(x) * (f(v, "loadVar", 60) / 100) + (1 - f(v, "loadVar", 60) / 100) * 70;
const baseline = 5 + (100 - load) * 0.08;
const optimized = f(v, "valveType", 0) ? 2 + (100 - load) * 0.02 : baseline;
return [baseline, optimized];
}
if (oid === "O10") {
const temp = oat(x, f(v, "oatMean", 18), 6);
const active = temp < 21 && f(v, "dewPoint", 10) < 12;
const demand = 100 * loadCurve(x);
return [demand, active ? demand * 0.15 : demand];
}
if (oid === "O11") {
const residual = f(v, "residual", 27);
const low = f(v, "overnightLow", 16);
if (x < 4) return [residual - 1, residual - 1];
if (x < 7.5) {
const pf = clamp((x - 4) / 2.5, 0, 1);
return [residual, residual - pf * Math.max(0, residual - 2 - low)];
}
return [23, 23];
}
if (oid === "O12") {
const occ = f(v, "peakOcc", 80) * occupancyCurve(x);
return [100, clamp(30 + occ * 0.7, 30, 100)];
}
if (oid === "O13") {
const density = f(v, "peakDensity", 55) * occupancyCurve(x);
const speed = clamp(25 + density * 0.75, 25, 100);
return [100, (speed / 100) ** 3 * 100];
}
if (oid === "O14") {
const load = (f(v, "loadAmp", 75) / 100) * loadCurve(x);
const speed = clamp(100 * ((40 + load * 60) / 95), 30, 100);
return [100, (speed / 100) ** 3 * 100];
}
if (oid === "O15") return [95, clamp((oat(x, f(v, "ambientMean", 20), 6) - 2) * 2.8, 25, 100)];
if (oid === "O16") {
const load = (f(v, "loadAmp", 75) / 100) * loadCurve(x);
return [100, clamp(100 * (1 - f(v, "idlePct", 20) / 100) * (0.4 + 0.6 * load), 20, 100)];
}
if (oid === "O17") return [100 + x * 1.6, 100 - (f(v, "coordScore", 55) / 100) * x * 3.2];
if (oid === "O18") return [100 + x * 0.3, 100 - (f(v, "coverage", 50) / 100) * x * 0.9];
if (oid === "O19") {
const base = 100 + x * 1.2;
return [base, base * (1 - clamp(f(v, "freq", 4) / 12, 0, 1))];
}
if (oid === "O20") {
const baseline = clamp(100 - x * 4, 40, 100);
const factor = f(v, "accessCtrl", 1) * 0.5 + clamp(f(v, "backupFreq", 4) / 12, 0, 1) * 0.5;
return [baseline, clamp(100 - x * 4 * (1 - factor), 40, 100)];
}
return [100, 100];
}
function seriesFor(oid, sliders, xType) {
const n = xType === "month" ? 12 : 24;
const out = [];
for (let x = 0; x < n; x++) {
const [b, o] = pointAt(oid, x, sliders);
out.push({ x, baseline: Math.round(b * 1000) / 1000, optimized: Math.round(o * 1000) / 1000 });
}
return out;
}
function metricsFor(oid, pts, v) {
const ab = avg(pts.map((p) => p.baseline));
const ao = avg(pts.map((p) => p.optimized));
const last = pts[pts.length - 1] || { baseline: 0, optimized: 0 };
if (oid === "O1") {
const bh = pts.filter((p) => p.baseline > 0).length;
const oh = pts.filter((p) => p.optimized > 0).length;
const red = bh ? ((bh - oh) / bh) * 100 : 0;
return [
{ label: "Baseline runtime", value: `${bh} h/day` },
{ label: "Optimized runtime", value: `${oh} h/day` },
{ label: "Operating hours cut", value: pct(red) },
];
}
if (oid === "O2") {
const red = ab ? ((ab - ao) / ab) * 100 : 0;
return [
{ label: "Baseline avg output", value: `${ab.toFixed(0)}%` },
{ label: "Optimized avg output", value: `${ao.toFixed(0)}%` },
{ label: "Est. HVAC energy cut", value: pct(red) },
];
}
if (oid === "O3") {
const red = ab ? ((ab - ao) / ab) * 100 : 0;
return [
{ label: "Baseline signal (high-select)", value: `${ab.toFixed(1)}°C` },
{ label: "Optimized signal (weighted)", value: `${ao.toFixed(1)}°C` },
{ label: "Over-cooling reduced", value: pct(red) },
];
}
if (oid === "O4") {
const red = ab ? clamp(((ab - ao) / ab) * 100, 0, 10) : 0;
return [
{ label: "Avg stages — baseline", value: ab.toFixed(1) },
{ label: "Avg stages — optimized", value: ao.toFixed(1) },
{ label: "Chiller-hours saved", value: pct(red) },
];
}
if (oid === "O5")
return [
{ label: "Baseline fan power", value: "100%" },
{ label: "Optimized fan power", value: `${ao.toFixed(0)}%` },
{ label: "Fan energy saved", value: pct(clamp(100 - ao, 0, 30)) },
];
if (oid === "O6")
return [
{ label: "Baseline flow temp", value: "82°C" },
{ label: "Optimized avg flow temp", value: `${ao.toFixed(0)}°C` },
{ label: "Boiler efficiency gain", value: `${clamp((82 - ao) * 0.15, 0, 5).toFixed(1)}%` },
];
if (oid === "O7") {
const per = f(v, "compType", 0) ? 4.5 : 2.5;
return [
{ label: "Baseline CHW temp", value: "6.5°C" },
{ label: "Optimized avg CHW temp", value: `${ao.toFixed(1)}°C` },
{ label: "Chiller energy saved", value: pct(clamp((ao - 6.5) * per, 0, 15)) },
];
}
if (oid === "O8") {
const sav = clamp((ab - ao) * 2.5, 0, 15);
return [
{ label: "Baseline CW temp", value: `${ab.toFixed(1)}°C` },
{ label: "Optimized avg CW temp", value: `${ao.toFixed(1)}°C` },
{ label: "Chiller energy saved", value: pct(sav) },
];
}
if (oid === "O9") {
const sav = clamp((ab - ao) * 3, 0, 15);
return [
{ label: "Baseline efficiency loss", value: `${ab.toFixed(1)}%` },
{ label: "Optimized efficiency loss", value: `${ao.toFixed(1)}%` },
{ label: "Compressor energy saved", value: pct(sav) },
];
}
if (oid === "O10") {
const hours = pts.filter((p) => p.optimized < p.baseline * 0.9).length;
const sav = ab ? clamp(((ab - ao) / ab) * 100, 0, 20) : 0;
return [
{ label: "Economy cycle active", value: `${hours} h/day` },
{ label: "Optimized avg compressor load", value: `${ao.toFixed(0)}%` },
{ label: "Compressor energy saved", value: pct(sav) },
];
}
if (oid === "O11") {
const window = pts.filter((p) => p.x >= 4 && p.x < 7.5);
const minOpt = window.length ? Math.min(...window.map((p) => p.optimized)) : f(v, "residual", 27);
const residual = f(v, "residual", 27);
return [
{ label: "Residual temp", value: `${residual.toFixed(1)}°C` },
{ label: "Purged down to", value: `${minOpt.toFixed(1)}°C` },
{ label: "Start-up energy saved", value: pct(clamp((residual - minOpt) * 4, 0, 20)) },
];
}
if (oid === "O12") {
const est = Math.round(400 + (f(v, "peakOcc", 80) / 100) * (f(v, "co2SP", 800) - 400));
return [
{ label: "Optimized avg OA flow", value: `${ao.toFixed(0)}%` },
{ label: "Peak CO₂ estimate", value: `${est} ppm` },
{ label: "Ventilation energy saved", value: pct(clamp(100 - ao, 0, 20)) },
];
}
if (oid === "O13")
return [
{ label: "Baseline fan power", value: "100%" },
{ label: "Optimized avg fan power", value: `${ao.toFixed(0)}%` },
{ label: "Fan energy saved", value: pct(clamp(100 - ao, 0, 80)) },
];
if (oid === "O14")
return [
{ label: "Baseline pump power", value: "100%" },
{ label: "Optimized avg pump power", value: `${ao.toFixed(0)}%` },
{ label: "Pumping energy saved", value: pct(clamp(100 - ao, 0, 30)) },
];
if (oid === "O15") {
const sav = ab ? clamp(((ab - ao) / ab) * 100, 0, 30) : 0;
return [
{ label: "Baseline fan power", value: `${ab.toFixed(0)}%` },
{ label: "Optimized avg fan power", value: `${ao.toFixed(0)}%` },
{ label: "Condenser fan energy saved", value: pct(sav) },
];
}
if (oid === "O16")
return [
{ label: "Baseline pump power", value: "100%" },
{ label: "Optimized avg pump power", value: `${ao.toFixed(0)}%` },
{ label: "CW pump energy saved", value: pct(clamp(100 - ao, 0, 30)) },
];
if (oid === "O17") {
const sav = last.baseline ? clamp(((last.baseline - last.optimized) / last.baseline) * 100, 0, 50) : 0;
return [
{ label: "Year-end index — no plan", value: last.baseline.toFixed(0) },
{ label: "Year-end index — with plan", value: last.optimized.toFixed(0) },
{ label: "Total energy saved", value: pct(sav) },
];
}
if (oid === "O18") {
const cov = f(v, "coverage", 50);
const sav = last.baseline ? clamp(((last.baseline - last.optimized) / last.baseline) * 100, 0, 10) : 0;
return [
{ label: "Total energy saved", value: pct(sav) },
{ label: "Est. NABERS star gain", value: `+${((cov / 100) * 0.5).toFixed(1)} ★` },
{ label: "Training coverage", value: `${cov.toFixed(0)}%` },
];
}
if (oid === "O19") {
const sav = last.baseline ? clamp(((last.baseline - last.optimized) / last.baseline) * 100, 0, 20) : 0;
return [
{ label: "Year-end index — reactive", value: last.baseline.toFixed(0) },
{ label: "Year-end index — maintained", value: last.optimized.toFixed(0) },
{ label: "HVAC energy saved", value: pct(sav) },
];
}
if (oid === "O20") {
const sav = clamp((last.optimized - last.baseline) * 0.2, 0, 10);
return [
{ label: "Settings retained — baseline", value: `${last.baseline.toFixed(0)}%` },
{ label: "Settings retained — managed", value: `${last.optimized.toFixed(0)}%` },
{ label: "HVAC energy saved", value: pct(sav) },
];
}
const sav = ab ? clamp(((ab - ao) / ab) * 100, 0, 80) : 0;
return [
{ label: "Baseline avg", value: ab.toFixed(1) },
{ label: "Optimized avg", value: ao.toFixed(1) },
{ label: "Guide reduction", value: pct(sav) },
];
}
g.OehPhysics = { seriesFor, metricsFor, clamp };
})(window);