| Time (UTC) | +Source | +Channel | +Metric | +Observed | +Expected | +Delta | +State | +Audit | +
|---|---|---|---|---|---|---|---|---|
| + {new Date(e.timestamp_utc).toISOString().replace('T', ' ').slice(0, 19)} + | +{e.source} | +{e.channel} | +{e.metric} | ++ {e.observed_value.toFixed(1)} {e.unit} + | ++ {e.expected_value != null ? `${e.expected_value.toFixed(1)} ${e.unit}` : '—'} + | +0 ? 'var(--gp-green)' : 'var(--gp-text-muted)', + }}> + {e.delta != null ? `${e.delta > 0 ? '+' : ''}${e.delta.toFixed(1)} ${e.unit}` : '—'} + | ++ + {e.state} + + | ++ {e.audit_hash} + | +
- Telemetry deviation analysis — actual vs. expected output comparison across the fleet.
- Asset values sourced from offline SCADA fixture (West Texas wind + Mojave solar, 2025-06-05T20:00Z).
- See apps/api/tests/fixtures/scada_fleet_snapshot.json → _provenance.
+ Actual vs. expected output — physics-model residual per asset.
+ Source: offline SCADA fixture (West Texas wind + Mojave solar, 2025-06-05T20:00Z).
apps/api/tests/fixtures/scada_fleet_snapshot.json → _provenance.
- Z-score deviation analysis on {DEMO_ASSETS.length} SCADA-sourced assets comparing - actual output vs. expected from the physics model. WTG-MCW-002 shows a deviation - (underproduction due to pitch controller deviation). + Z-score residual analysis — {DEMO_ASSETS.length} assets, actual vs. physics-model expected.
+ Connector state — source freshness, sample counts, quality codes, and protocol status. + All connectors are read-only. No command or control paths. +
+| Connector | +Protocol | +State | +Samples | +Notes | +
|---|---|---|---|---|
| + {c.connector} + | ++ {c.protocol} + | +
+ |
+ + {c.sample_count ?? '—'} + | ++ {c.error + ? {c.error} + : PROTOCOL_DESCRIPTIONS[c.protocol] ?? ''} + | +
| = 0 ? GREEN : '#ef4444', + color: m.residual >= 0 ? GREEN : '#f87171', }}> {m.residual > 0 ? '+' : ''}{m.residual.toLocaleString()} | diff --git a/docs/analysis-guide.md b/docs/analysis-guide.md index 6496cdb..afe4700 100644 --- a/docs/analysis-guide.md +++ b/docs/analysis-guide.md @@ -55,7 +55,7 @@ Each signal type decays at its own rate: A signal observed 4 hours ago contributes less confidence to the analysis than one observed 10 minutes ago. This prevents stale inputs from being treated with the same weight as fresh ones. -**What this means for engineers:** If input telemetry is stale, the confidence scores going into the structural state will be lower. The audit trace shows which signals were fresh and which were aged. +Stale telemetry reduces confidence scores. The audit trace records freshness per signal. --- @@ -69,11 +69,11 @@ Structural state compresses signal scores into three site-level quantities: | `data_quality` | 0–1 measure of signal completeness and freshness | | `derating_risk` | 0–1 estimate of the probability that the site is running below expected capacity | -**What this means for engineers:** +Instrumentation reference: - A `data_quality` of 0.95 means signals are fresh and mostly complete. - A `data_quality` of 0.50 means significant signals are stale, missing, or in disagreement — the forecast context should be treated as degraded. -- A `derating_risk` of 0.60 with a negative residual suggests the site may be operating below expected output for the current conditions. +- A `derating_risk` of 0.60 with a negative residual indicates output below model expectation for current conditions. --- diff --git a/docs/connector-strategy.md b/docs/connector-strategy.md new file mode 100644 index 0000000..deab7fe --- /dev/null +++ b/docs/connector-strategy.md @@ -0,0 +1,117 @@ +# Connector Strategy + +Dispatch Layer uses read-only instrumentation connectors to ingest measured state +from utility and industrial systems. Connectors expose timestamps, quality codes, +values, and metadata. They do not produce language, advice, findings, +recommendations, or instructions. + +## Connector categories + +### Operational data connectors + +Ingest SCADA/plant/utility telemetry. + +| Connector | Protocol | Purpose | Phase | +|---------------|-----------------|----------------------------------|-------| +| OPC UA | IEC 62541 | SCADA interoperability | 1 | +| MQTT | MQTT 3.1/5.0 | Edge telemetry stream | 1 | +| AWS SiteWise | REST/SDK | Industrial asset time-series | 1 | +| S3/Parquet | S3 + Apache | Historical archive and replay | 1 | +| Modbus TCP | Modbus | Legacy industrial equipment | 2 | +| DNP3 | IEEE 1815 | Electric utility telemetry | 2 | +| IEC 61850 | IEC 61850 | Substation / grid automation | 3 | +| C37.118/PMU | IEEE C37.118 | Phasor measurement unit | 3 | +| PI/AVEVA | PI Web API | Historian integration | 2 | +| InfluxDB | InfluxDB HTTP | Time-series export | 2 | +| Kafka | Kafka protocol | Streaming event backbone | 2 | + +### Platform observability connectors + +Instrument Dispatch Layer itself. + +| Connector | Protocol | Purpose | Phase | +|--------------------|-----------|----------------------------------|-------| +| OpenTelemetry/OTLP | OTLP | API latency, traces, metrics | 1 | +| Prometheus | HTTP | Metric scraping | 2 | + +## Read-only guarantee + +All connectors implement **read-only** paths only: + +``` +subscribe — passive listener, no acknowledgement or control +read — snapshot value read +query — historical range query +replay — deterministic historical playback from archive +``` + +The following are **not implemented** and must not be added: + +``` +write — value write +command — operational command +setpoint — setpoint change +control — any operational control path +``` + +## Unified output model + +All connectors normalise output to `TelemetrySample`: + +```python +@dataclass +class TelemetrySample: + source_id: str + channel_id: str + timestamp_utc: datetime + value: float | str | bool | None + unit: str | None + quality: Quality # GOOD | UNCERTAIN | BAD | MISSING | STALE + ingest_timestamp_utc: datetime + asset_id: str | None = None + source_timestamp_utc: datetime | None = None + tags: dict[str, str] = field(default_factory=dict) + audit_hash: str = "" # auto-computed SHA-256 +``` + +No English interpretation. Just samples. + +## Connector Matrix (UI) + +The Pipeline State page renders a live connector matrix: + +| Connector | Protocol | State | Samples | Latency p95 | Quality % | +|--------------------|--------------|---------|--------:|------------:|----------:| +| OTEL_COLLECTOR | OTLP | RUNNING | 7 | 48 ms | 100% | +| OPCUA_SCADA | OPC UA | RUNNING | 5 | — | 100% | +| MQTT_GATEWAY | MQTT | RUNNING | 4 | — | 75% | +| SITEWISE_PROD | AWS SiteWise | RUNNING | 4 | — | 75% | +| S3_PARQUET_ARCHIVE | S3/Parquet | RUNNING | 5 | — | 100% | + +## Phase 1 roadmap (current) + +- [x] OpenTelemetry/OTLP — platform observability +- [x] OPC UA — read-only SCADA (fixture + contract test) +- [x] MQTT — edge telemetry stream (fixture + contract test) +- [x] AWS IoT SiteWise — asset property values (fixture + contract test) +- [x] S3/Parquet — historical archive replay (fixture + contract test) +- [x] `/api/v1/connectors/state` endpoint +- [x] `PipelineState` frontend page + +## Phase 2 roadmap + +- [ ] Modbus TCP adapter skeleton +- [ ] DNP3 adapter skeleton +- [ ] PI/AVEVA Web API adapter +- [ ] InfluxDB adapter +- [ ] TimescaleDB adapter +- [ ] Prometheus remote read +- [ ] Kafka consumer adapter + +## Phase 3 roadmap + +- [ ] IEC 61850 model import +- [ ] C37.118 / synchrophasor stream +- [ ] Full OPC UA browse + subscription +- [ ] Kafka temporal playback archive +- [ ] Topology import adapter diff --git a/docs/data-policy.md b/docs/data-policy.md index 5b5ae8e..f936689 100644 --- a/docs/data-policy.md +++ b/docs/data-policy.md @@ -100,7 +100,7 @@ See `packages/domain/src/dispatchlayer_domain/telemetry.py` for the canonical mo --- -## Architecture summary +## Architecture Overview ``` Weather / grid / market APIs Hardware telemetry @@ -114,7 +114,7 @@ Weather / grid / market APIs Hardware telemetry ↓ Root-cause ranking ↓ - Recommendations + audit trace + Signal events + audit trace ``` The demo can run offline with fixtures, but the runtime architecture is built around diff --git a/docs/product-boundary.md b/docs/product-boundary.md new file mode 100644 index 0000000..1e89648 --- /dev/null +++ b/docs/product-boundary.md @@ -0,0 +1,139 @@ +# Product Boundary + +Dispatch Layer is an instrumentation and visualization console for utility-grade +SCADA telemetry, forecast envelopes, residual fields, spectral transforms, +temporal playback, source integrity, and audit metadata. + +## What Dispatch Layer renders + +``` +values — numeric measurements with units +series — time-ordered sequences +bands — p10 / p50 / p90 forecast envelopes +deltas — observed − expected +states — NOMINAL / WATCH / HIGH / CRITICAL / STALE / MISSING / CONFLICT +timestamps — source, server, ingest +threshold crossings — band violations, z-score thresholds +source status — freshness, quality code, latency, integrity % +residuals — signed error field +spectra — harmonic amplitude by frequency +coherence — frequency-domain agreement +coverage — fraction of actuals inside forecast band +calibration — bias, MAE, RMSE, MAPE +latency — p50 / p95 / p99 ingest and API latency +integrity — freshness, missingness, duplicate, conflict rates +audit hashes — SHA-256 of data + config + model version +playback frames — replayable historical state snapshots +``` + +## What Dispatch Layer does not produce + +``` +recommendations +findings +insights +summaries +reports +suggested actions +next steps +task cards +operator instructions +advice +generated explanations +narrative descriptions +"what this means" sections +risk-if-ignored prose +chatbot behavior +assistant behavior +natural-language generated reporting +``` + +## Language constraint + +Every text string in the UI must behave like one of: + +- instrument label +- table header +- field name +- unit +- route title +- status enum +- audit key +- timestamp +- axis label + +No paragraph-style interpretation. No helpful prose. No generated English-language reporting. + +## Read-only boundary + +All industrial connectors are read-only. Dispatch Layer implements: + +- subscribe / read / query / replay + +It does not implement: + +- write +- command +- setpoint +- control action +- dispatch instruction +- breaker operation + +This is a deliberate architectural constraint, not a gap. + +## Correct data model + +```ts +type TelemetrySample = { + source_id: string; + channel_id: string; + asset_id?: string; + timestamp_utc: string; + value: number | string | boolean | null; + unit?: string; + quality: "GOOD" | "UNCERTAIN" | "BAD" | "MISSING" | "STALE"; + source_timestamp_utc?: string; + ingest_timestamp_utc: string; + tags?: Record