Digital Twin Water
Spin up a working water treatment digital twin in 30 minutes. Three.js 3D scene, live sensor simulation, MQTT-ready, rule-based alerts. Runs entirely in the browser
Ask AI about Digital Twin Water
Powered by Claude Β· Grounded in docs
I know everything about Digital Twin Water. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
Water Treatment Digital Twin β Starter Kit

β Live Demo Β· Three.js Β· MQTT Β· No backend Β· No database Β· Runs entirely in the browser

What is this
A starter kit that lets any developer spin up a working digital twin of a water treatment plant in under 30 minutes β with live sensor simulation, real-time 3D visualization, a rule engine with trend detection, multi-sensor analysis charts, webhook alerts, flexible payload mapping, Sparkplug B support, process KPIs, and Claude Desktop integration via MCP.
No Docker, no server, no auth. Fork it, swap in your sensors, connect your real MQTT broker.
Quick Start
git clone https://github.com/j03rul4nd/digital-twin-water.git
cd digital-twin-water
npm install
npm run dev
Open http://localhost:5173 β choose your data source and the simulator starts immediately.
Features
| Feature | Description |
|---|---|
| 3D visualization | Procedural plant model. Meshes glow when alerts fire. ISA-101 color coding. |
| Rule engine | 15 rules evaluated every 500ms. Threshold + trend detection via linear regression. |
| Alert history | Resolved alerts move to History with duration and timestamp β not just a live list. |
| Multi-sensor analysis | Compare up to 6 sensors side-by-side. Synchronized cursors, zoom/pan, event markers, minimap, before/after comparison, PNG + CSV export. |
| Sensor detail modal | Per-sensor 3-minute chart with zone-colored line segments, hover crosshair, stale feed detection, and collapsible high-precision history table. |
| Event markers | Alert and scenario timestamps appear as vertical flag lines on every chart for instant visual correlation. |
| Webhook alerts | POST to Slack, Discord, n8n, Make, Zapier when alerts fire. Configured from UI. |
| Payload mapping | Auto-detect, flat, or custom field mapping. Connect any broker format without code. |
| Sparkplug B | Native Sparkplug B decode (Ignition, Cirrus Link, modern PLCs). No extra deps. |
| Incident simulator | Trigger fault scenarios from the UI. 5 scenarios, 30s duration, auto-reset. |
| Process KPIs | Throughput, chlorination efficiency, time-in-warning, backwash count, and more. Financial KPIs: OEE, cost/mΒ³, session cost, risk score. |
| Financial analytics | Per-sensor OEE, cost/unit, degradation countdown, volatility (CV), Sharpe ratio, economic impact. Configurable from sensor detail modal, KPI panel, or settings. Persisted in localStorage. |
| Replay mode | Scrub through the last 6 minutes of sensor history with a timeline bar. Pause live data, step frame-by-frame, or drag to any point. All panels (telemetry, alerts, charts) render from the historical snapshot. |
| Adaptive anomaly detection | Z-score baseline engine learns each sensor's rolling 2-minute mean and std. Fires statistical anomaly alerts when a reading exceeds 2β2.5Ο from baseline. 30-second cooldown per sensor prevents alert spam. |
| Claude Desktop | MCP server lets Claude query sensor data, alerts, KPIs and trends in real time. |
| UI-configurable | Broker, credentials, webhooks, payload mapping β all from the dashboard, no code. |
| Startup flow | Explicit data source selection on load. Simulation never starts without user action. |
Multi-Sensor Analysis Panel
Click β Compare in the topbar to open the analysis panel.
- Add up to 6 sensors from the left sidebar
- Synchronized crosshair: hover over any chart to see the value on all charts at the same timestamp
- Zoom & pan: scroll wheel to zoom, drag to pan β all charts stay in sync
- Time window: quick-select 30s / 1m / 2m / All from the header toolbar
- Event markers: vertical dashed lines where alerts fired (amber = warning, red = danger) β see exactly what the sensors were doing when the alert triggered
- Minimap navigator: 28px overview strip below each chart; click or drag to navigate the zoom window
- Analytics sidebar: per-sensor stats (ΞΌ, Ο, min, max, p95, n), trend direction, Pearson correlations between all visible pairs
- Before/After comparison: zoom into any window to see first-half vs. second-half mean delta and significance per sensor
- Export: CSV with all series, clipboard (TSV), chart config (JSON), PNG snapshot
- β¬ Cost layer: toggleable dashed overlay on each sensor chart showing cumulative cost accumulation with a secondary β¬ Y-axis; disabled when costPerUnit is off
- β Corr layer: Pearson correlation between sensor values and economic impact shown in the analytics sidebar; color-coded by strength (
|r| < 0.3green,< 0.7amber,β₯ 0.7red) - β‘ Impact layer: combined economic impact chart (sum of impact2h across all active sensors); only visible when β₯2 sensors are active; synchronized zoom and crosshair; zone bands at β¬0 / β¬10 / β¬50 thresholds
Connect your real MQTT broker
1. Click Configure & Connect β in the MQTT panel on the right side.
2. Fill in Broker URL, Username, Password, Plant ID β Test & Connect β
3. Publish to wtp/plant/{plantId}/sensors:
{
"timestamp": 1234567890123,
"readings": {
"inlet_flow": 142.3,
"raw_turbidity": 4.2,
"coag_ph": 7.1,
"filter_1_dp": 98.0,
"filter_2_dp": 102.5,
"filtered_turbidity": 0.28,
"chlorine_dose": 1.8,
"residual_chlorine": 0.45,
"tank_level": 67.0,
"outlet_pressure": 4.2
}
}
If your broker publishes a different format, click β Payload to configure field mapping. Paste a sample message and the auto-analyzer suggests mappings for you.
Sparkplug B
If your devices use Sparkplug B (Ignition, Cirrus Link, modern PLCs), the adapter detects it automatically from the topic pattern spBv1.0/... and decodes the Protobuf payload natively. No extra libraries, no configuration needed.
Works with
ws://andwss://brokers. For mutual TLS, you need a proxy β see docs/mqtt-production.md.
Full setup guide: docs/mqtt-production.md
Webhook alerts
Click β‘ Webhooks in the topbar to configure alert notifications.
| Event | Fires when |
|---|---|
alert.danger | A danger-level alert activates |
alert.warning | A warning-level alert activates |
alert.resolved | Any alert clears |
Payload (verified working with webhook.site, Slack, Discord, n8n, Make):
{
"event": "alert.danger",
"timestamp": 1774739174739,
"plant": "plant-01",
"alert": {
"id": "chlorine_deficit",
"severity": "danger",
"sensorIds": ["inlet_flow", "chlorine_dose"],
"message": "Chlorine dose not scaling with flow β disinfection deficit risk",
"active": true
}
}
Use the Test β button in the webhook form to verify your URL before saving.
Claude Desktop integration
Connect Claude Desktop to query your plant in real time.
# Terminal 1 β dashboard
npm run dev
# Terminal 2 β MCP bridge
node mcp-bridge-server.js
Configure claude_desktop_config.json:
{
"mcpServers": {
"wtp-digital-twin": {
"command": "node",
"args": ["/absolute/path/to/digital-twin-water/mcp-server.js"]
}
}
}
Then ask Claude things like:
- "What's the current status of the water treatment plant?"
- "Are there any active danger alerts? What's causing them?"
- "What's the trend for filter_1_dp over the last 2 minutes?"
- "How efficient has the chlorination been? What's the throughput?"

Full setup guide: docs/claude-desktop-setup.md
Adding your own alert rules
// src/sensors/RuleEngine.js β add to RULES[]
// Threshold rule
{
id: 'high_pressure', severity: 'warning',
sensorIds: ['outlet_pressure'],
message: 'Distribution pressure too high',
condition: (readings) => readings.outlet_pressure > 6.5,
},
// Trend rule β linear regression over a time window
{
id: 'pressure_rising', severity: 'warning',
sensorIds: ['outlet_pressure'],
message: 'Distribution pressure rising fast',
condition: (readings, state) => {
const trend = state.getTrend('outlet_pressure', 60); // last 60 seconds
if (!trend || trend.samples < 10) return false;
return trend.direction === 'rising' && trend.slope > 0.05;
},
},
getTrend() returns { slope, delta, deltaRel, direction, samples, mean, first, last }.
Adding your own sensors
// src/sensors/SensorConfig.js
{
id: 'my_sensor', label: 'My Sensor', unit: 'bar',
rangeMin: 0, rangeMax: 10,
normal: { low: 2, high: 8 },
warning: { low: 1, high: 9 },
danger: { low: 0, high: 10 },
}
// src/sensors/SensorSceneMap.js
my_sensor: ['mesh_pump_station'],
Architecture
sensor.worker.js (Web Worker β isolated from render loop)
β 500ms snapshots + 5 incident scenarios
βΌ
DataSourceManager β state machine: none | simulation | mqtt
β orchestrates Worker β MQTT transitions + SensorState.reset()
β StartupModal β user picks source explicitly on first load
βΌ
main.js β SensorState.update() β single source of truth + history buffer
β EventBus.emit(SENSOR_UPDATE)
β
ββββΆ RuleEngine threshold rules + trend rules (getTrend)
β β + adaptive Z-score rules (BaselineEngine)
β ββββΆ AlertPanel active list + history section
β ββββΆ AlertSystem emissive glow on 3D meshes
β ββββΆ WebhookManager POST to configured URLs
β ββββΆ EventMarkers time-indexed store of alert timestamps
β
ββββΆ KPIEngine throughput Β· chlorination eff Β· time-in-warning Β· backwashes
β β + financial KPIs: sessionOEE Β· avgCostPerM3 Β· sessionCostTotal Β· financialRiskScore
β ββββΆ KPIPanel modal with bar chart + stats grid + Financial section (4 cards)
β ββββΆ MCPBridge push state to mcp-bridge-server every 1s
β
ββββΆ ReplayController β β Replay button in topbar
β ReplayBar (timeline scrubber, step buttons, exit)
β Consumers: TelemetryPanel, SceneUpdater, AlertPanel, SensorDetailModal
β
ββββΆ SceneUpdater ColorMapper β mesh.material.color per tick
ββββΆ TelemetryPanel rows β click β SensorDetailModal (live SVG chart, stale detection, financial analytics)
ββββΆ MultiChartPanel β β Compare button
ChartStore (zoom, hover, series β observable store)
AnalyticsEngine (stats, derivative, anomalies, correlation, LTTB)
EventMarkers (vertical flag lines at alert timestamps)
FinancialConfig (localStorage-persisted config, subscribe/notify pattern)
FinancialAnalytics (pure: OEE, cost/unit, degradation, volatility, Sharpe, impact)
MQTTAdapter (real broker)
β topic = spBv1.0/... β SparkplugParser.parse() (Protobuf decode)
β topic = standard β PayloadMapper.transform() (auto/flat/custom)
ββββΆ same SensorState β same pipeline β zero downstream changes
Claude Desktop β mcp-server.js β mcp-state.json β mcp-bridge-server β MCPBridge
Sensors
| ID | Measurement | Unit | Stage |
|---|---|---|---|
inlet_flow | Inlet Flow Rate | mΒ³/h | Intake |
raw_turbidity | Raw Water Turbidity | NTU | Intake |
coag_ph | Coagulation pH | pH | Coagulation |
filter_1_dp | Filter #1 Differential Pressure | mbar | Filtration |
filter_2_dp | Filter #2 Differential Pressure | mbar | Filtration |
filtered_turbidity | Filtered Water Turbidity | NTU | Post-filtration |
chlorine_dose | Chlorine Dose | mg/L | Chlorination |
residual_chlorine | Residual Chlorine | mg/L | Distribution |
tank_level | Clearwell Tank Level | % | Storage |
outlet_pressure | Distribution Pressure | bar | Distribution |
Roadmap
V1.1 β Historical charts Β· Incident simulator Β· Trend detection
V1.2 β Webhook alerts Β· Flexible payload mapping
V1.3 β Sparkplug B Β· Process KPIs Β· Claude Desktop MCP integration
V1.4 β DataSourceManager Β· StartupModal Β· SensorDetailModal v2 Β· MultiChartPanel (multi-sensor analysis, event markers, minimap, before/after comparison, PNG export) Β· AnalyticsEngine Β· ChartStore Β· EventMarkers
V1.5 β Financial analytics module Β· FinancialAnalytics.js (6 pure functions: OEE, cost/unit, degradation, volatility, Sharpe, economic impact) Β· FinancialConfig.js (localStorage-persisted singleton) Β· renderFinancialConfigUI.js (shared config renderer) Β· SensorDetailModal β inline config panel Β· KPIEngine 4 financial KPIs Β· KPIPanel Financial section Β· ConfigModal financial section Β· MultiChartPanel 3 economic layers (β¬ Cost, β Corr, β‘ Impact)
V1.6 β Replay mode Β· ReplayController (REPLAY_ENTERED / REPLAY_SCRUBBED / REPLAY_EXITED events) Β· ReplayBar timeline scrubber UI Β· all panels render from historical snapshots during replay Β· EVENT_CONTRACT_VERSION '3'
V1.7 β Adaptive anomaly detection Β· BaselineEngine.js (pure stateless Z-score functions: computeBaseline, isAnomaly, formatAnomalyMessage) Β· 5 ADAPTIVE_RULES wired into RuleEngine Β· BASELINE_UPDATED event every 5s Β· 30s cooldown per sensor Β· EVENT_CONTRACT_VERSION '4'
V2.0 β Planned
feature/ai-advisor: TinyLlama via WebLLM, natural language process diagnostics (~700MB, opt-in)
Stack
| Layer | Tech | Why |
|---|---|---|
| Bundler | Vite | HMR without reloading WebGL |
| 3D | Three.js | WebGL2, procedural model, no assets |
| Realtime | Web Worker + MQTT.js | Worker isolates render loop |
| Protocols | MQTT + Sparkplug B | Native Protobuf decode, no extra deps |
| Charts | Pure SVG | No chart library β full control, no bundle weight |
| Map | Leaflet + OSM | Free, no API key |
| AI integration | MCP protocol | Claude Desktop reads live plant data |
| Deploy | GitHub Pages / Vercel | Static build, free |
Built by
Joel Benitez Β· LinkedIn Β· Medium
Star the repo if this saved you from another heavyweight platform. Issues and PRs welcome.
