diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Kpi/KpiMetricAggregation.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Kpi/KpiMetricAggregation.cs
index df661750..a463a875 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Kpi/KpiMetricAggregation.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Kpi/KpiMetricAggregation.cs
@@ -42,10 +42,10 @@ public enum KpiRollupAggregation
/// Keying by (source, metric) — not metric alone — is deliberate:
/// deliveredLastInterval is emitted by both
/// and ,
-/// so a metric name is not globally unique. Every metric string is taken from the
-/// emitting source verbatim (shared constants where they exist,
-/// otherwise the source's own private literal), so this catalog stays a faithful mirror
-/// of what actually lands in KpiSample.Metric.
+/// so a metric name is not globally unique. Every catalogued metric string is now a shared
+/// constant — the emitting source consumes the same constant — so an
+/// emitter rename is a compile error, not a silent Gauge downgrade (R4), and
+/// this catalog stays a faithful mirror of what actually lands in KpiSample.Metric.
///
///
/// The catalog stores only the pairs; anything
@@ -57,12 +57,6 @@ public enum KpiRollupAggregation
///
public static class KpiMetricAggregationCatalog
{
- // Metric literals for the sources that keep their names private (no shared
- // KpiMetrics constant exists for these). Charted metrics use the KpiMetrics catalog.
- private const string SiteCallAuditDeliveredLastInterval = "deliveredLastInterval";
- private const string SiteHealthAlarmEvalErrors = "alarmEvalErrors";
- private const string SiteHealthEventLogWriteFailures = "eventLogWriteFailures";
-
///
/// The (source, metric) pairs classified as
/// (sum-per-hour). Every other emitted metric is a .
@@ -99,11 +93,11 @@ public static class KpiMetricAggregationCatalog
{
(KpiSources.NotificationOutbox, KpiMetrics.NotificationOutbox.DeliveredLastInterval),
(KpiSources.SiteCallAudit, KpiMetrics.SiteCallAudit.FailedLastInterval),
- (KpiSources.SiteCallAudit, SiteCallAuditDeliveredLastInterval),
+ (KpiSources.SiteCallAudit, KpiMetrics.SiteCallAudit.DeliveredLastInterval),
(KpiSources.SiteHealth, KpiMetrics.SiteHealth.ScriptErrors),
- (KpiSources.SiteHealth, SiteHealthAlarmEvalErrors),
+ (KpiSources.SiteHealth, KpiMetrics.SiteHealth.AlarmEvalErrors),
(KpiSources.SiteHealth, KpiMetrics.SiteHealth.DeadLetters),
- (KpiSources.SiteHealth, SiteHealthEventLogWriteFailures),
+ (KpiSources.SiteHealth, KpiMetrics.SiteHealth.EventLogWriteFailures),
};
///
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Kpi/KpiMetrics.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Kpi/KpiMetrics.cs
index d2230812..ffedf771 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Kpi/KpiMetrics.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Kpi/KpiMetrics.cs
@@ -18,11 +18,16 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Kpi;
/// not a rename — changing any value would orphan historical samples.
///
///
-/// Only the metrics a trend page actually charts are catalogued here. Sources may emit
-/// additional internal metrics (e.g. the Notification Outbox stuckCount /
-/// oldestPendingAgeSeconds, the Site Call Audit deliveredLastInterval /
-/// stuck / oldestPendingAgeSeconds, and the bulk of the Site Health catalog)
-/// that no page references; those stay private to their source until a page charts them.
+/// This catalog now carries two kinds of metric: those a trend page charts, and
+/// those the classifies for the hourly rollup fold
+/// (e.g. SiteCallAudit/deliveredLastInterval, SiteHealth/alarmEvalErrors,
+/// SiteHealth/eventLogWriteFailures) even if no page charts them yet. The C4/R4 lesson
+/// is that any metric named in two places needs one shared symbol: a private
+/// literal on the emitter plus a matching private literal in the catalog drifts silently on a
+/// rename. Sources may still keep genuinely single-use internal metrics (e.g. the Notification
+/// Outbox stuckCount / oldestPendingAgeSeconds, the Site Call Audit stuck /
+/// oldestPendingAgeSeconds, and the bulk of the Site Health catalog) private until a page
+/// or the catalog references them.
/// Constants are grouped by source via nested static classes mirroring
/// .
///
@@ -65,6 +70,11 @@ public static class KpiMetrics
/// Cached calls that failed permanently in the last KPI interval.
public const string FailedLastInterval = "failedLastInterval";
+
+ /// Cached calls delivered in the last KPI interval. Not charted by a trend
+ /// page today, but named in the rollup aggregation catalog as a Rate metric — so it
+ /// is a shared symbol to keep the emitter and catalog in lock-step (R4).
+ public const string DeliveredLastInterval = "deliveredLastInterval";
}
///
@@ -98,6 +108,16 @@ public static class KpiMetrics
/// Script-execution error count reported by the site.
public const string ScriptErrors = "scriptErrors";
+ /// Alarm-evaluation error count reported by the site. Named in the rollup
+ /// aggregation catalog as a Rate metric — shared symbol so an emitter rename is a
+ /// compile error, not a silent Gauge downgrade (R4).
+ public const string AlarmEvalErrors = "alarmEvalErrors";
+
+ /// Event-log write-failure count reported by the site. Named in the rollup
+ /// aggregation catalog as a Rate metric — shared symbol so an emitter rename is a
+ /// compile error, not a silent Gauge downgrade (R4).
+ public const string EventLogWriteFailures = "eventLogWriteFailures";
+
/// Summed store-and-forward buffer depth across all buffers.
public const string SfBufferDepth = "sfBufferDepth";
}
diff --git a/src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/Kpi/SiteHealthKpiSampleSource.cs b/src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/Kpi/SiteHealthKpiSampleSource.cs
index 1c2701ca..501cb967 100644
--- a/src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/Kpi/SiteHealthKpiSampleSource.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/Kpi/SiteHealthKpiSampleSource.cs
@@ -40,7 +40,7 @@ public sealed class SiteHealthKpiSampleSource : IKpiSampleSource
private const string MetricConnectionsUp = "connectionsUp";
private const string MetricConnectionsDown = KpiMetrics.SiteHealth.ConnectionsDown;
private const string MetricScriptErrors = KpiMetrics.SiteHealth.ScriptErrors;
- private const string MetricAlarmEvalErrors = "alarmEvalErrors";
+ private const string MetricAlarmEvalErrors = KpiMetrics.SiteHealth.AlarmEvalErrors;
private const string MetricSfBufferDepth = KpiMetrics.SiteHealth.SfBufferDepth;
private const string MetricDeadLetters = KpiMetrics.SiteHealth.DeadLetters;
private const string MetricParkedMessages = "parkedMessages";
@@ -48,7 +48,7 @@ public sealed class SiteHealthKpiSampleSource : IKpiSampleSource
private const string MetricEnabledInstances = "enabledInstances";
private const string MetricDisabledInstances = "disabledInstances";
private const string MetricAuditBacklogPending = "auditBacklogPending";
- private const string MetricEventLogWriteFailures = "eventLogWriteFailures";
+ private const string MetricEventLogWriteFailures = KpiMetrics.SiteHealth.EventLogWriteFailures;
private readonly ICentralHealthAggregator _aggregator;
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteCallAudit/Kpi/SiteCallAuditKpiSampleSource.cs b/src/ZB.MOM.WW.ScadaBridge.SiteCallAudit/Kpi/SiteCallAuditKpiSampleSource.cs
index bfab3778..f7a2d994 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteCallAudit/Kpi/SiteCallAuditKpiSampleSource.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteCallAudit/Kpi/SiteCallAuditKpiSampleSource.cs
@@ -42,7 +42,7 @@ public sealed class SiteCallAuditKpiSampleSource : IKpiSampleSource
private const string MetricBuffered = KpiMetrics.SiteCallAudit.Buffered;
private const string MetricParked = KpiMetrics.SiteCallAudit.Parked;
private const string MetricFailedLastInterval = KpiMetrics.SiteCallAudit.FailedLastInterval;
- private const string MetricDeliveredLastInterval = "deliveredLastInterval";
+ private const string MetricDeliveredLastInterval = KpiMetrics.SiteCallAudit.DeliveredLastInterval;
private const string MetricStuck = "stuck";
private const string MetricOldestPendingAgeSeconds = "oldestPendingAgeSeconds";
diff --git a/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Kpi/KpiMetricAggregationTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Kpi/KpiMetricAggregationTests.cs
index 7bbae2bf..a3cb9b08 100644
--- a/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Kpi/KpiMetricAggregationTests.cs
+++ b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Kpi/KpiMetricAggregationTests.cs
@@ -148,4 +148,22 @@ public class KpiMetricAggregationTests
KpiRollupAggregation.Rate,
KpiMetricAggregationCatalog.Resolve(KpiSources.SiteCallAudit, "deliveredLastInterval"));
}
+
+ // ---- Task 8: catalog metric literals promoted to shared KpiMetrics constants (R4) ----
+
+ [Fact]
+ public void PromotedMetricConstants_LockHistoricalPersistedValues()
+ {
+ // Persisted data contract — these are symbol promotions, NOT renames.
+ Assert.Equal("deliveredLastInterval", KpiMetrics.SiteCallAudit.DeliveredLastInterval);
+ Assert.Equal("alarmEvalErrors", KpiMetrics.SiteHealth.AlarmEvalErrors);
+ Assert.Equal("eventLogWriteFailures", KpiMetrics.SiteHealth.EventLogWriteFailures);
+ }
+
+ [Theory]
+ [InlineData(KpiSources.SiteCallAudit, KpiMetrics.SiteCallAudit.DeliveredLastInterval)]
+ [InlineData(KpiSources.SiteHealth, KpiMetrics.SiteHealth.AlarmEvalErrors)]
+ [InlineData(KpiSources.SiteHealth, KpiMetrics.SiteHealth.EventLogWriteFailures)]
+ public void Resolve_PromotedRatePairs_StillClassifyAsRate(string source, string metric)
+ => Assert.Equal(KpiRollupAggregation.Rate, KpiMetricAggregationCatalog.Resolve(source, metric));
}