Factory console: demo fleet of one Duplex system and one Classic
Deploy test site / deploy (push) Successful in 51s

Three presses for now (D1-A, D1-B on one Newbilt Duplex system, C1
Classic) instead of the plan's two Duplex systems. Order arrivals scaled
to the smaller fleet (about 3,000 to 4,600 good records a day); press
row fills the width; landing page text updated.
This commit is contained in:
grabowski committed 2026-10-16 10:29:39 +07:00
1 parent 9ed51f4b22
commit dd7b749af9
2 files changed
+11 -10

No files matched your search

+9 -8
View File
@@ -115,12 +115,12 @@ document.documentElement.setAttribute("data-theme",t);})();
.ok { color: var(--ok); } .bad { color: var(--bad); } .warn { color: var(--warn); }
/* presses */
.presses { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(262px, 100%), 1fr)); gap: 12px; }
.presses { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(262px, 100%), 1fr)); gap: 12px; }
.pcard { display: flex; flex-direction: column; gap: 9px; background: var(--card); border: 1px solid var(--line); border-radius: 14px; padding: 12px 14px; color: var(--ink); text-decoration: none; min-width: 0; position: relative; }
.pcard:hover { border-color: var(--stroke); text-decoration: none; }
.pc-h { display: flex; align-items: center; gap: 8px; }
.pc-h b { font-family: var(--mono); font-size: 15px; }
.pc-h .mute { font-size: 12px; }
.pc-h .mute { font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.pst { margin-left: auto; font-size: 11.5px; font-weight: 700; padding: 1px 8px; border-radius: 99px; background: var(--card2); color: var(--mute); white-space: nowrap; }
.lamp { width: 9px; height: 9px; border-radius: 50%; background: var(--faint); flex: none; }
.s-run .lamp { background: var(--ok); box-shadow: 0 0 0 3px var(--ok-s); animation: pulse 1.6s ease-in-out infinite; }
@@ -343,7 +343,7 @@ document.documentElement.setAttribute("data-theme",t);})();
<div class="fac" id="factory">
<aside class="side">
<nav id="fnav" aria-label="Factory console"></nav>
<div class="sidenote"><b>What this is.</b> The factory application from the digital factory plan (working name cnx-factory): every machine, instrument and operator check writes to one append-only log, and the customer portal is a scoped view of the same records.<br><br><b>Simulated:</b> all orders, values and IDs. <b>From the plan:</b> stages and the three artist gates, the inspection layers, Newbilt cycle times, two shifts 06:00 to 22:00, the launch fleet. Limits are illustrative: the acceptance specifications are still to be written.</div>
<div class="sidenote"><b>What this is.</b> The factory application from the digital factory plan (working name cnx-factory): every machine, instrument and operator check writes to one append-only log, and the customer portal is a scoped view of the same records.<br><br><b>Simulated:</b> all orders, values and IDs. <b>From the plan:</b> stages and the three artist gates, the inspection layers, Newbilt cycle times, two shifts 06:00 to 22:00. The demo runs one Duplex system and one Classic. Limits are illustrative: the acceptance specifications are still to be written.</div>
</aside>
<main id="fmain"></main>
</div>
@@ -425,7 +425,8 @@ const NOW0=(()=>{const d=new Date();d.setHours(10,5,0,0);return d.getTime();})()
const S={t:NOW0-60*DAY,speed:20,run:true,warm:true,orders:[],byId:{},presses:[],lots:[],use:{black:null,clear:null},pend:{black:0,clear:0},regrind:[],feed:[],aud:[],led:[],ledN:0,day:{k:0},days:[],nextArr:0,seq:{o:1040,p:300,lot:2400,rg:880},
ui:{view:"factory",fpage:"floor",arg:null,fhash:"floor",filter:"active",q:"",cust:null}};
function mkPress(id,sys,kind){return {id,sys,kind,st:"off",next:0,until:0,order:null,set:null,since:0,why:"",tel:[],lc:30000,op:"",res:0};}
S.presses=[mkPress("D1-A","Duplex 1","duplex"),mkPress("D1-B","Duplex 1","duplex"),mkPress("D2-A","Duplex 2","duplex"),mkPress("D2-B","Duplex 2","duplex"),mkPress("C1","Classic","classic")];
/* demo fleet for now: one Duplex system (two presses on one extruder set) and one Classic */
S.presses=[mkPress("D1-A","Duplex","duplex"),mkPress("D1-B","Duplex","duplex"),mkPress("C1","Classic","classic")];
const pById=id=>S.presses.find(p=>p.id===id);
/* ================= ledger: append-only, hash-chained ================= */
@@ -635,7 +636,7 @@ function arrivals(t){
while(S.nextArr<=t){
const at=S.nextArr;newOrder(at);
const queued=S.orders.reduce((a,o)=>a+((o.si===SI.queue||o.si===SI.run)?o.qty-o.good:0),0);
S.nextArr=at-Math.log(1-rnd())*(DAY/3.6)*(queued>18000?2.6:1);
S.nextArr=at-Math.log(1-rnd())*(DAY/2.2)*(queued>11000?2.6:1);
}
}
function step(t){dayRoll(t);advanceLots(t);for(const p of S.presses)pressAdvance(p,t);advanceOrders(t);arrivals(t);S.t=t;}
@@ -794,7 +795,7 @@ function pressCard(p){
<div class="pc-m"><span>Stamper set ${set?set.n:"-"}: ${set?fI(set.plays):0} of ${set?fI(set.life):0} plays</span>${bar(set?set.plays/set.life:0,set&&set.plays/set.life>0.9?"warn":"")}</div>`;
else body=`<div class="pc-empty mute">${p.st==="off"?"Off for the night.":"No order mounted."}</div>`;
const stats=p.st==="run"&&tl?`<span>${f1(tl.cyc)} s cycle</span><span>${Math.round(3600/tl.cyc)} an hour</span><span>steam ${Math.round(tl.steam)} °C</span>`:`<span>${esc(pstate(p))}</span>`;
return `<a class="pcard s-${p.st}" href="#press=${p.id}"><div class="pc-h"><span class="lamp"></span><b>${p.id}</b><span class="mute">${p.kind==="duplex"?"Newbilt "+p.sys:"Newbilt Classic"}</span><span class="pst">${PST[p.st]}</span></div>${body}<div class="pc-t">${stats}</div></a>`;
return `<a class="pcard s-${p.st}" href="#press=${p.id}"><div class="pc-h"><span class="lamp"></span><b>${p.id}</b><span class="mute">${p.kind==="duplex"?"Newbilt Duplex, press "+p.id.slice(-1):"Newbilt Classic"}</span><span class="pst">${PST[p.st]}</span></div>${body}<div class="pc-t">${stats}</div></a>`;
}
function alerts(){const A=[];
for(const p of S.presses){if(p.st==="stop")A.push(["bad",`${p.id} stopped: ${p.why}`,`Back in about ${dur(p.until-S.t)}. Logged by the press PLC.`,"press="+p.id]);
@@ -818,7 +819,7 @@ const PAGES={};
PAGES.floor={
layout:()=>`<div class="ph"><div><div class="eyebrow">Factory console</div><h1>Press floor, live</h1></div><p class="mute" id="fShift"></p></div>
<div class="kpis" id="fK"></div>
<div class="sh"><h2>Presses</h2><span class="mute">The launch fleet in the plan: two Newbilt Duplex systems and one Classic</span></div>
<div class="sh"><h2>Presses</h2><span class="mute">One Newbilt Duplex system (two HRP 12 presses) and one Newbilt Classic</span></div>
<div class="presses" id="fP"></div>
<div class="cols2"><section class="card"><div class="ch"><h3>Optical inspection, every record</h3><span class="mute">record key: stamper · press · cycle time</span></div><div id="fFeed"></div></section>
<section class="card"><div class="ch"><h3>Needs attention</h3></div><div id="fAl"></div></section></div>
@@ -901,7 +902,7 @@ PAGES.press={
const o=p.order,tel=p.tel,last=tel[tel.length-1],bp=S.day.bp[p.id]||{g:0,r:0};
const tl=(lbl,key,unit,lo,hi,fmt)=>`<div><div class="ch" style="margin:0"><span>${lbl}</span><b class="mono">${last?fmt(last[key])+" "+unit:"-"}</b></div>${spark(tel.map(x=>x[key]),lo,hi)}</div>`;
return {
prH:`<div class="ph"><div><div class="eyebrow">${p.kind==="duplex"?"Newbilt "+p.sys+", HRP 12 press":"Newbilt Classic, automatic"}</div><h1>${p.id} <span class="chip ${p.st==="run"?"g-out":p.st==="stop"||p.st==="nopvc"?"g-gate":"g-queue"}">${PST[p.st]}</span></h1></div><p class="mute">${esc(pstate(p))}${p.op?" · "+p.op:""}</p></div>`,
prH:`<div class="ph"><div><div class="eyebrow">${p.kind==="duplex"?"Newbilt Duplex, HRP 12 press "+p.id.slice(-1):"Newbilt Classic, automatic"}</div><h1>${p.id} <span class="chip ${p.st==="run"?"g-out":p.st==="stop"||p.st==="nopvc"?"g-gate":"g-queue"}">${PST[p.st]}</span></h1></div><p class="mute">${esc(pstate(p))}${p.op?" · "+p.op:""}</p></div>`,
prG:`<section class="card"><div class="ch"><h3>Mounted</h3>${o?`<a href="#order=${o.id}">${o.id}</a>`:""}</div>${o?`<div class="pc-o" style="margin-bottom:10px">${discSVG(o,54,false)}<div><b>${esc(o.title)}</b><div class="mute">${esc(o.artist)}</div><div class="mute">${esc(fmtS(o))}</div></div></div><dl class="kv"><dt>Progress</dt><dd>${fI(o.good)} good of ${fI(o.qty)}, ${fI(o.rej)} rejected</dd><dt>Stampers</dt><dd class="mono">${p.set?p.set.st.map(s=>s.id).join(" / "):"-"}</dd><dt>Plays</dt><dd>${p.set?fI(p.set.plays)+" of "+fI(p.set.life)+" expected life":"-"}</dd><dt>Compound</dt><dd class="mono">${o.lots.slice(-1)[0]||"-"}</dd><dt>Labels</dt><dd class="mono">${o.labelLot||"-"}</dd></dl>`:'<p class="mute">Nothing mounted.</p>'}</section>
<section class="card"><div class="ch"><h3>Today on this press</h3></div><div class="stats3"><div><b>${fI(bp.g)}</b><span>good</span></div><div><b>${fI(bp.r)}</b><span>rejected</span></div><div><b>${f1(pct(bp.r,bp.g+bp.r))} %</b><span>reject rate</span></div></div><p class="note">${p.kind==="duplex"?"Cycle about 30 s (Newbilt): load by hand 4 to 6.5 s, close, steam, cool, open, eject.":"Cycle 28 s (Newbilt Classic flyer), fully automatic."} Unplanned stops average one per four running hours in this simulation.</p></section>
<section class="card" style="grid-column:1/-1"><div class="ch"><h3>Telemetry, last ${tel.length} cycles</h3><span class="mute">values simulated around the plan's 10 bar / 180 °C steam</span></div><div class="cols3">${tl("Cycle time","cyc","s",26,34,f1)}${tl("Steam temperature","steam","°C",176,184,f1)}${tl("Steam pressure","bar","bar",9.5,10.5,f2)}${tl("Cooling water supply","cool","°C",15,21,f1)}${tl("Cake weight","cake","g",o?cakeW(o)-5:140,o?cakeW(o)+5:160,f1)}</div></section>
+2 -2
View File
@@ -117,8 +117,8 @@ document.documentElement.setAttribute("data-theme",t);})();
<li>
<a href="demos/factory/">
<strong>Factory console and order status (investor demo)</strong>
<span>The factory application on a simulated plant day: five
presses with live counters, every record through optical
<span>The factory application on a simulated plant day: a Newbilt
Duplex system and a Classic with live counters, every record through optical
inspection, audio samples, the material lab gate, stamper
metrology and the hash-chained event log. Switch to the customer
portal to follow the same orders: steps and approvals, copies