Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,37 @@
### [CORE] AxoCauseAnalyzer + AxoIncidentBarView — probable-cause ranking and persistent operator incident bar over AxoMessenger

**Note:** Additive change in `src/core/src/AXOpen.Core/AxoMessenger/Static/` and `src/core/src/AXOpen.Core.Blazor/AxoMessenger/Static/`. No PLC source change required for application opt-in; the analyzer reads only fields `AxoMessenger` already exposes. Branch: `feat-most-probable-failure-cause`.

- feat: `AxoCauseAnalyzer` (`AXOpen.Messaging.Static`) — heuristic ranking layered on `AxoMessageProvider`. Scores each Error-or-above active messenger by severity (operator-actionability map: Critical=1.0, Error=0.9, ProgrammingError=0.85, Warning=0.6, Potential=0.4, Info=0.1), burst-root (earliest `Risen` within sliding `BurstWindow`, default 8 s, anchored on latest `Risen`), twin-tree topology (container-Symbol-prefix `DownstreamCount`), acknowledgement state, and age decay. `Changed` event fires only on top-cause symbol flip. Anti-strobe hold (`HoldDuration` default 2 s) suppresses PLC-cycle mid-read empty publishes. Static factory `AxoCauseAnalyzer.Create(AxoMessageProvider, options?, nowUtc?)` wires the adapter pipeline.
- feat: `AxoIncidentBarView` (`AXOpen.Core.Blazor`, namespace `AXOpen.Messaging.Static`) — sticky in-flow Blazor component (zero height when idle, no layout reflow) that renders the top probable cause as a severity-colored bar (`shadow-glow-{danger|warning|info}` + flat `bg-{token}/15` tint — color token matches the glow). `animate-pulse` for Critical/ProgrammingError until acknowledged. Click-to-expand panel shows top-5 candidates with score, evidence (burst-root, downstream count), optimistic Acknowledge, admin-gated Restore (`<AuthorizeView Roles="Administrator">`). Adaptive polling — 750 ms when any Error+ active, 2500 ms idle — using two-tier batch reads via the existing provider. `aria-live="polite"` for accessibility.
- feat: `AxoIncidentBarPresenter` — pure-logic seam. Exposes `CurrentState` (visibility, severity bucket, pulse flag, rows, ack-pending markers) and static Tailwind class mappers (`GlowClass`, `BadgeClass`, `BackgroundClass`, `ToSeverityBucket`). All decision logic is xUnit-testable without rendering or twin scaffolding. `IncidentBarSeverity` enum split into `Critical` / `Error` / `Warning` / `Info` / `None`.
- feat: `IRankableMessage` + `AxoMessengerRankableAdapter` — delegate-driven projection (`Func<string>`, `Func<DateTime>`, etc.) so tests fake fields without standing up an `AxoMessenger`. Delegates re-invoke on each access so the analyzer sees the latest batch-read value.
- feat: Default `CauseSeverityFloor = eAxoMessageCategory.Error`. Warning/Potential/Info still count in `ActiveCount` / `PeakSeverity` (global indicators stay accurate) but never enter the cause ranking. Configurable via `AxoCauseAnalyzerOptions`.
- fix: Topology heuristic now compares CONTAINER symbols (strip last segment), not messenger Symbols directly. Earlier draft treated sibling messengers as unrelated even when their parent components nested. New test `Topology_uses_container_prefix_not_messenger_symbol_prefix` locks the realistic twin-tree shape.
- feat: Showcase `AxoIncidentBarExample.st` (nested `Station` → `Drive` → `Encoder` + `Conveyor` → `Sensor`, each with its own `AxoMessenger` and condition flag) plus `Pages/core/AxoIncidentBar.razor` (Live Bar tab with operator controls, Topology code tab with snippet refs, Heuristic tab with ranking formula). NavMenu entry under Core; search registry entry.
- feat: Template `axopen.template.simple` — `MainLayout.razor` mounts `<AxoIncidentBarView Provider="@_alarmProvider" />` once per layout, cascades the provider so `GeneralAlarms.razor` consumes the same provider instance instead of walking the twin tree a second time.
- feat: Template `tailwind.css` extended with `@source` paths for `axopen/src/core/src/AXOpen.Core/**/*.cs` and `AXOpen.Core.Blazor/**/*.razor` so future bar class additions get JIT-compiled.
- docs: Added `src/core/docs/AxoIncidentBar.md` (ranking formula, severity floor, Blazor mount pattern). Cross-linked from `src/core/docs/toc.yml` under "Messengers (Alarms)". Appended `0.56.0` entry to `src/core/docs/CHANGELOG.md` (minor bump from `0.55.1`, GitVersion `next-version` updated).
- test: 28 new tests in `src/core/tests/AXOpen.Core.Tests/Messaging/` — `AxoCauseAnalyzerTests` (15: severity-floor, severity map, burst window, sliding burst, topology container prefix, ack de-prio, TopN clamp, anti-strobe hold, Changed event semantics, age decay), `AxoMessengerRankableAdapterTests` (2: projection + delegate re-invocation), `AxoCauseAnalyzerFactoryTests` (2: null guard + empty provider), `AxoIncidentBarPresenterTests` (26: visibility, severity bucket, pulse, additional count, rows, ack-pending, idle hysteresis, CSS class mapping, background-color-matches-glow invariant). Total: 69/69 green.

**Impact:**
- Operators see a single severity-colored bar above the layout with the highest-confidence root cause of the current incident, instead of scanning a flat alarm list.
- The bar collapses to zero height when no Error+ is active — no permanent UI cost when the line is healthy.
- Applications opt in by mounting one component in their `MainLayout.razor` and creating one provider. No ST source changes; no `AxoMessenger` API change.
- Engineers writing custom HMI surfaces can consume `AxoCauseAnalyzer` directly (events + `TopCause` / `ProbableCauses` properties) without the Blazor view.

**Risks/Review:**
- Severity-floor default is `Error`. Warning-only incidents do NOT raise the bar (intentional: operator noise reduction). Override via `AxoCauseAnalyzerOptions.CauseSeverityFloor` if a deployment needs Warning-level surfacing.
- `bg-{token}/15` and `shadow-glow-{token}` Tailwind classes must be present in the host app's compiled `momentum.css`. The template app's `tailwind.css` `@source` glob now covers the relevant axopen paths — rebuild required (`tailwind.ps1`) on first integration.
- Bar polling uses `System.Threading.Timer`; `IAsyncDisposable` cleans it up. If hosted in a Blazor Server circuit with frequent reconnects, monitor for orphaned timers under stress.
- `AxoIncidentBarView` consumes `AuthenticationStateProvider` cascaded parameter for the admin-gated Restore button. If the host app routes the bar outside an `AuthorizeRouteView` boundary, the cascading parameter is `null` and Restore degrades to no-op (no exception).

**Testing:**
- `dotnet test src/core/tests/AXOpen.Core.Tests/` — 69/69 green (17 pre-existing + 52 messaging).
- `dotnet build src/core/src/AXOpen.Core.Blazor/` — Razor compiles clean.
- `dotnet build src/showcase/app/ix-blazor/showcase.blazor/` after `apax ib` — 0 errors. Showcase page navigates to `/core/AxoIncidentBar` and renders the live bar with the topology controls.
- `dotnet build axopen.template.simple/axpansion/server/` — 0 errors. `MainLayout` cascades the provider; `GeneralAlarms.razor` consumes it without creating a second instance.

### [KUKA] KRC5 raw data exchange, coordinate-mirror diagnostics, and auto-mode severity fix ([#1151](https://github.com/Inxton/AXOpen/pull/1151))

**Note:** KRC5-only change in `src/components.kuka.robotics/`. `AxoKrc5` now diverges from `AxoKrc4` in three respects — `AxoKrc4` is intentionally left unchanged in this PR. Fixes #1148.
Expand Down
2 changes: 1 addition & 1 deletion GitVersion.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mode: ContinuousDeployment
next-version: 0.55.1
next-version: 0.56.0
branches:
main:
regex: ^master$|^main$
Expand Down
152 changes: 152 additions & 0 deletions src/core/docs/AxoIncidentBar.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# AxoIncidentBar

*Probable-cause ranking + persistent operator-facing incident bar over `AxoMessenger`.*

`AxoCauseAnalyzer` ranks active `AxoMessenger` instances and surfaces the most
likely *cause* of the current failure pattern. `AxoIncidentBarView` renders the
top probable cause as a persistent, severity-colored bar at the top of the
application layout, with a click-to-expand panel listing additional candidates,
score breakdown, and acknowledgement controls.

The cause analyzer reads only what `AxoMessenger` already exposes — no PLC
source changes are required to opt in. See [AxoMessenger](AxoMessenger.md) for
the underlying messaging primitive.

---

## Why it exists

`AxoMessageProvider` returns a flat list of all active messengers. During a
real cascade failure (a sensor trips → conveyor stops → station enters E-stop)
the operator sees three simultaneous alarms with no indication of which one is
the *cause* versus a *symptom*. `AxoCauseAnalyzer` applies a heuristic to score
each active messenger and rank the most likely root cause to the top of the
list. `AxoIncidentBarView` then renders that ranking as a single, visually
unmissable bar so the operator's attention lands on the right thing first.

---

## Severity floor

By default only **Error**, **ProgrammingError**, and **Critical** messages
enter the cause ranking. Warnings, Potentials, and Infos still count toward
`AxoMessageProvider.ActiveMessagesCount` (so the existing badge indicators stay
accurate), but they never appear in the bar. The floor is configurable via
`AxoCauseAnalyzerOptions.CauseSeverityFloor`.

---

## Ranking formula

Each Error-or-above active messenger is scored:

```
Score = 0.40 * severity_weight(Category)
+ 0.30 * (is_burst_root ? 1 : 0)
+ 0.20 * log10(1 + DownstreamCount)
+ 0.10 * (is_acknowledged ? 0 : 1)
- 0.02 * minutes_since_risen
```

| Term | Meaning |
|------|---------|
| `severity_weight` | Critical=1.0, Error=0.9, ProgrammingError=0.85, Warning=0.6, Potential=0.4, Info=0.1 — operator-actionability map, not enum ordinals |
| `is_burst_root` | TRUE for the earliest `Risen` within the sliding `BurstWindow` (default 8 s, anchored on the latest `Risen`) — likely root of a cascade |
| `DownstreamCount` | Number of *other* active messengers whose container Symbol is a descendant of this messenger's container Symbol (twin-tree ownership) |
| `is_acknowledged` | Ack'd-but-still-active messages are de-prioritized but still listed (operator already saw them) |
| `minutes_since_risen` | Long-running alarms decay below freshly-risen peers |

**Anti-strobe**: when the source briefly reads empty mid-PLC-cycle, the
published top cause is held for `HoldDuration` (default 2 s) before clearing.

**Idle hysteresis**: the bar itself remains visible for `IdleHysteresis`
(default 2 s) after the last cause clears, then collapses to zero height.

---

# [CONTROLLER](#tab/controller)

## Nested twin-tree topology

The cause analyzer's topology heuristic identifies a parent component as
"owner" of its descendants' alarms via Symbol prefix. To exercise it, declare
messengers at multiple tree levels:

[!code-pascal[](../../showcase/app/src/core/AXOpen.Messaging/AxoIncidentBarExample.st?name=TopologyDeclaration)]

The Station container holds a Drive sub-component (which holds an Encoder
leaf) and a Conveyor sub-component (which holds a Sensor leaf). Each level
declares its own `AxoMessenger`.

## Activate the messenger

Standard `ActivateOnCondition` works unchanged — the analyzer never needs the
PLC code to know it exists:

[!code-pascal[](../../showcase/app/src/core/AXOpen.Messaging/AxoIncidentBarExample.st?name=StationActivate)]

When Station and Drive both fire at the same time, the analyzer detects that
Drive's container is a descendant of Station's container (Symbol-prefix check
stripped of the messenger's own segment), credits Station with `DownstreamCount = 1`,
and ranks Station above Drive even though Drive is `Critical` and Station is
`Error` — because Station owns more of the cascade.

# [BLAZOR](#tab/blazor)

## Create a provider

`AxoIncidentBarView` consumes an `AxoMessageProvider`. Create it once per
layout / circuit, scoped to the relevant twin object root:

[!code-csharp[](../../showcase/app/ix-blazor/showcase.blazor/Pages/core/AxoIncidentBar.razor?name=ProviderCreate)]

## Mount the bar

Drop `AxoIncidentBarView` into your layout (typically near the top of
`MainLayout.razor`, above `@Body`):

[!code-html[](../../showcase/app/ix-blazor/showcase.blazor/Pages/core/AxoIncidentBar.razor?name=BarMount)]

The bar is in-flow — it has zero height when no Error+ cause is active, so
nothing else needs to reflow. Severity drives the color (`shadow-glow-danger`
+ `bg-danger/15` for Error/Critical, `warning` and `info` for the other
buckets); Critical and ProgrammingError additionally animate with
`animate-pulse` until acknowledged.

## Parameters

| Parameter | Type | Default | Purpose |
|-----------|------|---------|---------|
| `Provider` | `AxoMessageProvider` | required | Source of active messengers |
| `Options` | `AxoCauseAnalyzerOptions?` | `null` (defaults) | Override `BurstWindow`, `HoldDuration`, `IdleHysteresis`, `TopN`, `CauseSeverityFloor` |
| `PlcLabel` | `string?` | `null` | Optional prefix for multi-PLC stacked deployments |
| `AllowRestore` | `bool` | `false` | Show admin-only "Restore parent task" button in expanded panel |
| `Class` | `string?` | `null` | Extra Tailwind classes appended to the outer card |
| `ActivePollingMs` | `int` | `750` | Tier-2 batch-read cadence while any Error+ active |
| `IdlePollingMs` | `int` | `2500` | Tier-1 lightweight cadence when idle |

***

## Multi-PLC deployments

Pass each PLC context as its own provider and stack the bars vertically. The
analyzer's topology check uses `Symbol` prefix per-PLC, which is naturally
correct (no cross-PLC parenting).

## Programmatic access

The analyzer can be used without the bar. `AxoCauseAnalyzer.Create(provider)`
returns an instance whose `TopCause`, `ProbableCauses`, `ActiveCount`,
`PeakSeverity`, and `Changed` event are usable from any C# host (custom HMI,
logging sinks, external monitoring). `AxoIncidentBarPresenter` provides a
pure-logic seam (no rendering) for custom UI shells.

## Testing

The analyzer is unit-tested via `IRankableMessage` — a thin abstraction over
`AxoMessenger` that takes plain delegates, so tests do not need twin
scaffolding. The full ranking spec is locked in
`axopen/src/core/tests/AXOpen.Core.Tests/Messaging/AxoCauseAnalyzerTests.cs`
and the presenter logic in `AxoIncidentBarPresenterTests.cs`.

See also [AxoMessenger](AxoMessenger.md) · [AxoLogger](AxoLogger.md).
12 changes: 12 additions & 0 deletions src/core/docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@
{axopen-version} replace this with the current settings in GitVersion.yml file.
-->

### 0.56.0

**New features:**
- Added `AxoCauseAnalyzer` (`AXOpen.Messaging.Static`) — heuristic probable-cause ranking layered on `AxoMessageProvider`. Scores active Error+ messengers by severity, burst-root (earliest within sliding `BurstWindow`), twin-tree topology (container-Symbol-prefix `DownstreamCount`), acknowledgement state, and age decay. Hold-cached against PLC-cycle strobe; `Changed` event fires only on top-cause symbol flip.
- Added `AxoIncidentBarView` (`AXOpen.Core.Blazor`, `AXOpen.Messaging.Static`) — sticky in-flow Blazor component that renders the top probable cause as a severity-colored bar with click-to-expand panel, optimistic acknowledge, admin-gated restore, `aria-live=polite` for accessibility, adaptive 750 ms (active) / 2500 ms (idle) polling cadence using two-tier batch reads.
- Added `AxoIncidentBarPresenter` — pure-logic seam exposing `CurrentState` (visibility, severity bucket, pulse flag, rows, ack-pending markers) and static Tailwind class mappers (`GlowClass`, `BadgeClass`, `BackgroundClass`) for custom UI shells.
- Added `IRankableMessage` + `AxoMessengerRankableAdapter` — delegate-driven projection of `AxoMessenger` so the analyzer can be unit-tested without twin scaffolding.
- Added showcase `AxoIncidentBarExample.st` (nested Station → Drive → Encoder + Conveyor → Sensor topology) and `Pages/core/AxoIncidentBar.razor` page demonstrating the bar end-to-end.

**Other:**
- Documentation: added `AxoIncidentBar.md` describing the ranking formula, severity floor (default Error), and Blazor mount pattern. Cross-linked from `AxoMessenger.md` via TOC.

### 0.43.0

**New features:**
Expand Down
4 changes: 3 additions & 1 deletion src/core/docs/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@
href : AxoStep.md
- name : AxoSequencerContainer
href : AxoSequencerContainer.md
- name : Messengers (Alarms)
- name : Messengers (Alarms)
items:
- name : AxoMessenger
href : AxoMessenger.md
- name : AxoIncidentBar
href : AxoIncidentBar.md
- name : Logging
items:
- name : AxoLogger
Expand Down
Loading
Loading