Skip to content

AxoCauseAnalyzer + AxoIncidentBarView: probable-cause ranking over AxoMessenger#1156

Merged
PTKu merged 5 commits into
devfrom
feat-most-probable-failure-cause
May 27, 2026
Merged

AxoCauseAnalyzer + AxoIncidentBarView: probable-cause ranking over AxoMessenger#1156
PTKu merged 5 commits into
devfrom
feat-most-probable-failure-cause

Conversation

@PTKu
Copy link
Copy Markdown
Contributor

@PTKu PTKu commented May 27, 2026

Summary

  • New AxoCauseAnalyzer ranks active Error+ AxoMessenger instances by severity, burst-root (sliding 8 s window), twin-tree topology (container-Symbol prefix), ack state, and age decay; Changed event fires only on top-cause symbol flip.
  • New AxoIncidentBarView Blazor component renders the top probable cause as a persistent severity-colored bar — zero height when idle, animate-pulse for Critical/ProgrammingError, click-to-expand panel with top-5 candidates, optimistic Acknowledge, admin-gated Restore.
  • Pure C# / Blazor opt-in — no AxoMessenger ABI change. Application mounts one component in MainLayout.razor and creates one provider per circuit.
  • 69/69 tests green (52 new in AXOpen.Core.Tests/Messaging/). Showcase page + nested topology example + docs + central/per-lib CHANGELOG. GitVersion bumped 0.55.10.56.0.

Changes from CHANGELOG

Workspace

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 StationDriveEncoder + ConveyorSensor, 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.simpleMainLayout.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.

Commits

feat:

  • ed7eaa3df Add AxoCauseAnalyzer and AxoIncidentBarPresenter for incident analysis
  • 47b41fd03 Implement AxoIncidentBar for incident analysis and visualization

docs:

  • 7035b4071 docs: add AxoIncidentBar.md + central/per-lib CHANGELOG + showcase tags

Other:

  • bf64be7b8 Refactor code structure for improved readability and maintainability

Diff stat

 CHANGELOG.md                                       |  34 +++
 GitVersion.yml                                     |   2 +-
 src/core/docs/AxoIncidentBar.md                    | 152 ++++++++++
 src/core/docs/CHANGELOG.md                         |  12 +
 src/core/docs/toc.yml                              |   4 +-
 .../AxoMessenger/Static/AxoIncidentBarView.razor   | 209 +++++++++++++
 .../AxoMessenger/Static/AxoCauseAnalyzer.cs        | 196 ++++++++++++
 .../AxoMessenger/Static/AxoIncidentBarPresenter.cs | 152 ++++++++++
 .../Static/AxoMessengerRankableAdapter.cs          |  41 +++
 .../AxoMessenger/Static/IRankableMessage.cs        |  15 +
 .../Messaging/AxoCauseAnalyzerFactoryTests.cs      |  29 ++
 .../Messaging/AxoCauseAnalyzerTests.cs             | 331 +++++++++++++++++++++
 .../Messaging/AxoIncidentBarPresenterTests.cs      | 262 ++++++++++++++++
 .../Messaging/AxoMessengerRankableAdapterTests.cs  |  60 ++++
 .../Pages/core/AxoIncidentBar.razor                | 195 ++++++++++++
 .../Services/Search/ShowcasePageRegistry.cs        |  19 ++
 .../ix-blazor/showcase.blazor/Shared/NavMenu.razor |   1 +
 src/showcase/app/src/ShowcaseContext.st            |   4 +
 .../core/AXOpen.Messaging/AxoIncidentBarExample.st | 157 ++++++++++
 src/styling/src/wwwroot/css/momentum.css           |   2 +-
 20 files changed, 1874 insertions(+), 3 deletions(-)

Test plan

  • dotnet test src/core/tests/AXOpen.Core.Tests/ — 69/69 green (15 analyzer, 26 presenter, 2 adapter, 2 factory, 17 pre-existing core).
  • apax ib from src/showcase/app/ regenerates twins cleanly after AxoIncidentBarExample.st + ShowcaseContext.st change.
  • dotnet build src/showcase/app/ix-blazor/showcase.blazor/ — 0 errors. Showcase razor page compiles.
  • Launch showcase app, navigate to /core/AxoIncidentBar, toggle _station_error + _drive_error together; confirm Station ranks above Drive via DownstreamCount, bar pulses for Critical until acknowledged, collapses within IdleHysteresis (2 s) after all flags cleared.
  • docfx build from docfx/[!code-pascal[]] (TopologyDeclaration, StationActivate) and [!code-csharp[]] / [!code-html[]] (ProviderCreate, BarMount) directives resolve; AxoIncidentBar.md renders under TOC "Messengers (Alarms)".
  • CHANGELOG entry (root + src/core/docs/CHANGELOG.md) covers all branch commits and flags the topology fix.

Generated by /pr-description-update. Last regenerated: 2026-05-27.

PTKu and others added 5 commits May 26, 2026 16:16
- Implemented AxoCauseAnalyzer to analyze and rank probable causes of incidents based on severity, acknowledgment status, and age.
- Introduced AxoIncidentBarPresenter to manage the display state of incident bars, including visibility, severity, and acknowledgment status.
- Created IRankableMessage interface and AxoMessengerRankableAdapter for message adaptation.
- Added unit tests for AxoCauseAnalyzer, AxoIncidentBarPresenter, and AxoMessengerRankableAdapter to ensure functionality and correctness.
- Defined AxoCauseAnalyzerOptions for configurable parameters like burst window and hold duration.
- Established event handling for changes in top causes to trigger UI updates.
- New axopen/src/core/docs/AxoIncidentBar.md describing ranking formula,
  severity floor, Blazor mount pattern
- toc.yml entry under Messengers (Alarms)
- Per-lib CHANGELOG 0.56.0 entry; GitVersion next-version 0.55.1 -> 0.56.0
- Central CHANGELOG PR-style entry covering analyzer/bar/presenter/adapter
  + showcase + template wiring + topology fix + 28 new tests
- Showcase razor BarMount + ProviderCreate tagged regions so docs reference
  real code via [!code-*[]] directives

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
private static string SafeMessageText(AxoMessenger m)
{
try { return m.GetMessageText(); }
catch { return string.Empty; }
ExpireStaleAckPending();
_presenter.Refresh();
}
catch { /* swallow: a bad cycle should not crash the bar */ }
Comment on lines +172 to +177
catch
{
_ackPendingSince.Remove(symbol);
_presenter.NotifyAckResolved(_state.TopCause.Message);
await InvokeAsync(StateHasChanged);
}
@PTKu PTKu merged commit 1a62392 into dev May 27, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant