Initial commit
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
# LINE-1 Digital Twin — customer demo
|
||||
|
||||
A live digital twin of a five-station production line: 3D view, streaming
|
||||
telemetry, OEE, trend-based failure prediction, injectable faults, what-if
|
||||
controls, and an LLM copilot that diagnoses the line from its own telemetry.
|
||||
|
||||
Built to be presented from a laptop, offline if necessary.
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Dashboard on **http://localhost:5173**, API on **http://localhost:8787**.
|
||||
For a single-port build with no dev server: `npm run serve`, then
|
||||
**http://localhost:8787**. Full setup detail is in
|
||||
[docs/installation.md](docs/installation.md).
|
||||
|
||||
The line starts **pre-warmed** — 30 simulated minutes of history, a full OEE
|
||||
window at a realistic ~88%, and anomaly baselines already learned. You are never
|
||||
presenting a dashboard that just booted.
|
||||
|
||||
## Read this first
|
||||
|
||||
- **[docs/installation.md](docs/installation.md)** — requirements, install, how to
|
||||
run it, the demo sequence, switching models, and troubleshooting.
|
||||
- **[docs/LINE-1-Digital-Twin-Demo.pptx](docs/LINE-1-Digital-Twin-Demo.pptx)** — the
|
||||
step-by-step demonstration deck. 16 slides, one per beat, with the presenter
|
||||
actions and script in the speaker notes. Every chart is measured data.
|
||||
- **[docs/demo-script.md](docs/demo-script.md)** — the same 6-minute run of show in
|
||||
text form: what to click, what to say, and the questions you will get.
|
||||
- **[docs/architecture.md](docs/architecture.md)** — where the customer's MQTT,
|
||||
OPC-UA or historian data plugs in.
|
||||
|
||||
## The demo in one paragraph
|
||||
|
||||
The line runs at a believable 88% OEE. You change the oven setpoint and watch a
|
||||
thermal model respond with a real time constant — that is what makes it a twin
|
||||
rather than a dashboard. You inject a bearing fault, run the clock at 60×, and the
|
||||
twin projects the alarm threshold *before* it is crossed. The copilot — which is
|
||||
never told what you injected — traces vibration to tool wear to reject rate to
|
||||
OEE. It drafts the work order. You replace the bearing and watch OEE recover. Then
|
||||
you switch the copilot to its offline rule engine and get the same diagnosis with
|
||||
no model and no network, which answers "is the AI making this up?" better than any
|
||||
claim could.
|
||||
|
||||
## What is honest about this
|
||||
|
||||
Worth knowing, because it is what makes the demo survive an engineer in the room.
|
||||
|
||||
- **OEE is ~88%, not 100%.** Micro-stops and unplanned stops are modelled, so the
|
||||
number moves for reasons you can name. Performance is capped at 100% by
|
||||
definition, so draining a WIP buffer can never produce an OEE above 100%.
|
||||
- **Prediction is trend extrapolation, not AI.** A least-squares fit over recent
|
||||
run time, projected to the threshold, suppressed when r² is too low to trust.
|
||||
Call it what it is; the AI story is the copilot.
|
||||
- **The copilot is not told which fault was injected.** Operator injection log
|
||||
lines are filtered out of its context. The diagnosis is earned from telemetry.
|
||||
- **Stations block and starve each other** through finite WIP buffers. Stop the
|
||||
packer and the backup propagates upstream until the whole line is blocked.
|
||||
- **A dropped sensor holds its last value and says so.** Charts stop rather than
|
||||
drawing a flat line that would read as a healthy steady state.
|
||||
- **The copilot always answers.** A dead key, an unreachable host, or a model that
|
||||
returns an empty response all degrade to the rule engine.
|
||||
|
||||
## Copilot providers and the settings page
|
||||
|
||||
Click **AI ⚙** in the header to open the settings page. It lists the models each
|
||||
provider actually offers — pulled live from the OpenRouter catalogue (400+, with
|
||||
context length and price per million) and from your local Ollama library — and
|
||||
lets you switch provider, model, temperature, output budget and reasoning effort
|
||||
without restarting anything. The dashboard keeps streaming behind it.
|
||||
|
||||
| Mode | First token | Notes |
|
||||
|---|---|---|
|
||||
| **Cloud** — OpenRouter | 2–35 s, highly variable | Best analysis. Default `stealth/ox-alpha`. |
|
||||
| **Local** — Ollama | ~1–2 s | Run `ollama serve`. Best for the live beat. |
|
||||
| **Offline rules** | instant | No network, no model. Deterministic. |
|
||||
|
||||
**Measure before you present.** Each model panel has a **Test** button that
|
||||
reports time-to-first-token and grades it: under 4 s is presentable live, 4–12 s
|
||||
means a noticeable pause, above that is follow-up-only. Cloud reasoning-model
|
||||
latency was measured between 2 s and 36 s on consecutive identical calls, so this
|
||||
is worth checking on the day rather than assuming.
|
||||
|
||||
Changes persist to `.copilot-settings.json` (gitignored), so a model chosen while
|
||||
preparing survives a restart. **Reset to defaults** discards it and falls back to
|
||||
`.env`. Settings never contain the API key — that stays in the environment.
|
||||
|
||||
Resolution order when provider is **Auto**: OpenRouter if a key is set, else
|
||||
Ollama if it answers, else the rule engine. The copilot panel's own selector still
|
||||
overrides per question.
|
||||
|
||||
Copy `.env.example` to `.env` for the initial defaults. Notes:
|
||||
|
||||
- `OLLAMA_HOST` is often set machine-wide to `0.0.0.0` by Ollama itself. That is a
|
||||
*bind* address and not dialable — the server normalizes it to `127.0.0.1`.
|
||||
- Thinking models (the Qwen3 family) are sent `think: false`. Without it they
|
||||
spend the whole token budget reasoning and return empty content.
|
||||
- `COPILOT_PROVIDER=fallback` forces the rule engine, for rehearsing the worst case.
|
||||
- Anything set in the settings page takes precedence over `.env` until you reset it.
|
||||
|
||||
## Verification
|
||||
|
||||
Two suites, no browser needed. Run both after any change to the model.
|
||||
|
||||
```bash
|
||||
node scripts/simcheck.mjs
|
||||
```
|
||||
|
||||
Checks the model tells the truth: signals stay in physical range, faults move OEE
|
||||
the right way in a controlled A/B against an unfaulted line, packer stoppage
|
||||
propagates upstream as blocking, a dropped sensor goes stale rather than to zero,
|
||||
setpoint changes are lagged, runs are reproducible from a seed, and the prediction
|
||||
arrives *before* the threshold is crossed (~435 s of simulated lead time).
|
||||
|
||||
```bash
|
||||
node scripts/apicheck.mjs # server must be running
|
||||
```
|
||||
|
||||
Checks the WebSocket stream, the control API, fault propagation into the stream,
|
||||
and that the copilot answers in every provider mode with answers grounded in real
|
||||
readings.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
server/
|
||||
sim/ line.js (part flow, WIP, blocking) · stations.js (physics)
|
||||
faults.js (injectable profiles) · kpi.js (OEE)
|
||||
analytics/ trend.js (least squares) · anomaly.js (frozen baselines)
|
||||
alarms.js (thresholds, latching, predictions)
|
||||
ingest/ source.js (the seam) · simulatedSource.js
|
||||
mqttSource.js, opcuaSource.js (documented stubs)
|
||||
ai/ copilot.js (providers) · context.js (prompt) · rules.js (offline)
|
||||
settings.js (validated, persisted runtime config)
|
||||
web/src/
|
||||
three/ Scene.jsx, Station.jsx, Conveyor.jsx
|
||||
panels/ KPIs, station strip, trend charts, alarms, what-if, copilot
|
||||
SettingsPage.jsx (provider/model picker + latency test)
|
||||
scripts/ dev.mjs, simcheck.mjs, apicheck.mjs
|
||||
```
|
||||
|
||||
The 3D scene loads **no external assets** — no HDRI, no CDN font, no texture. It
|
||||
is primitives and analytic lights, so it works in a room with no wifi.
|
||||
|
||||
## Notable design decisions
|
||||
|
||||
- **A server owns the simulation**, not the browser. It keeps the API key server-
|
||||
side, gives the ingest layer a real seam, and makes "swap in your historian"
|
||||
true rather than aspirational.
|
||||
- **Signals declare their analytics eligibility.** `cumulative` and `volatile`
|
||||
flags in `stations.js` exclude a signal from anomaly testing — normal tool wear
|
||||
accumulation is not an anomaly.
|
||||
- **Anomalies only alarm in the direction that is bad**, inferred from which
|
||||
thresholds a signal declares. A tool change dropping wear from 18% to 2% must
|
||||
not raise an alarm for being *better* than baseline.
|
||||
- **Separate RNG streams** for signal noise, stall decisions and reject rolls.
|
||||
Sharing one stream put the reject roll at a correlated phase in the sequence and
|
||||
measurably biased it — a 2.0% reject rate was firing at 5.7%.
|
||||
- **Single dark theme, deliberately.** This is an ops dashboard on a projector.
|
||||
Chart colours come from a validated palette; status colours are reserved and
|
||||
always paired with a text label, never carrying meaning by hue alone.
|
||||
Reference in New Issue
Block a user