diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Kpi/KpiSample.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Kpi/KpiSample.cs
new file mode 100644
index 00000000..f73d8f4a
--- /dev/null
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Kpi/KpiSample.cs
@@ -0,0 +1,55 @@
+namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Kpi;
+
+///
+/// A single point-in-time KPI measurement in the central KPI-history backbone
+/// (M6 "KPI History & Trends"). One row per (Source, Metric, Scope, ScopeKey)
+/// captured by the recorder singleton at a sampling instant — a tall / EAV row in
+/// the central KpiSample table.
+///
+///
+///
+/// Persistence-ignorant POCO; the EF Core mapping lives in the Configuration
+/// Database component. draws from
+/// and
+/// from
+/// ;
+/// is drawn from each owning source's own metric catalog.
+///
+///
+/// is a that carries counts exactly and
+/// ages as seconds. is UTC, like every timestamp in
+/// the system.
+///
+///
+public sealed class KpiSample
+{
+ /// Surrogate identity key assigned by the store.
+ public long Id { get; set; }
+
+ ///
+ /// Owning component / source — a value from
+ /// .
+ ///
+ public required string Source { get; set; }
+
+ /// Metric name, drawn from the owning source's metric catalog.
+ public required string Metric { get; set; }
+
+ ///
+ /// Scope discriminator — a value from
+ /// .
+ ///
+ public required string Scope { get; set; }
+
+ ///
+ /// Scope qualifier — the site id or node name the sample belongs to;
+ /// null for .
+ ///
+ public string? ScopeKey { get; set; }
+
+ /// Measured value — counts are exact; ages are expressed in seconds.
+ public double Value { get; set; }
+
+ /// The UTC instant at which the sample was captured.
+ public DateTime CapturedAtUtc { get; set; }
+}
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Kpi/IKpiSampleSource.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Kpi/IKpiSampleSource.cs
new file mode 100644
index 00000000..1c866fda
--- /dev/null
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Kpi/IKpiSampleSource.cs
@@ -0,0 +1,33 @@
+using ZB.MOM.WW.ScadaBridge.Commons.Entities.Kpi;
+
+namespace ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Kpi;
+
+///
+/// A DI-registered provider of KPI samples (M6 "KPI History & Trends"),
+/// implemented by each owning component and enumerated by the central recorder
+/// singleton at every sampling interval. The recorder stamps a single
+/// capturedAtUtc, fans out to every source's ,
+/// and bulk-writes the combined samples.
+///
+public interface IKpiSampleSource
+{
+ ///
+ /// The source this provider reports for — a value from
+ /// . Every
+ /// returned by carries this value
+ /// in .
+ ///
+ string Source { get; }
+
+ ///
+ /// Compute this source's KPIs as-of-now, stamping each returned sample's
+ /// with .
+ ///
+ /// The shared UTC capture instant for this sampling pass.
+ /// Cancellation token.
+ ///
+ /// A task resolving to the samples computed for this source; empty when there is
+ /// nothing to report.
+ ///
+ Task> CollectAsync(DateTime capturedAtUtc, CancellationToken cancellationToken = default);
+}
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/IKpiHistoryRepository.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/IKpiHistoryRepository.cs
new file mode 100644
index 00000000..d06fcb62
--- /dev/null
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/IKpiHistoryRepository.cs
@@ -0,0 +1,51 @@
+using ZB.MOM.WW.ScadaBridge.Commons.Entities.Kpi;
+using ZB.MOM.WW.ScadaBridge.Commons.Types.Kpi;
+
+namespace ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
+
+///
+/// Data access for the central KPI-history backbone (M6 "KPI History & Trends")
+/// — the tall / EAV KpiSample table in central MS SQL. Backs the recorder
+/// singleton's bulk write, the bucketed query path that feeds the reusable trend
+/// chart, and the retention purge. Implementation lives in the Configuration
+/// Database component.
+///
+public interface IKpiHistoryRepository
+{
+ ///
+ /// Bulk-inserts a batch of captured samples (one sampling pass across all
+ /// registered sources).
+ ///
+ /// The samples to record.
+ /// Cancellation token.
+ /// A task that represents the asynchronous write.
+ Task RecordSamplesAsync(IReadOnlyCollection samples, CancellationToken cancellationToken = default);
+
+ ///
+ /// Returns the raw points for one series in [fromUtc, toUtc], ordered by
+ /// ascending.
+ ///
+ /// Source identifier — a value from .
+ /// Metric name from the source's catalog.
+ /// Scope discriminator — a value from .
+ /// Scope qualifier (site id / node name); null for the Global scope.
+ /// Inclusive lower bound of the time window (UTC).
+ /// Inclusive upper bound of the time window (UTC).
+ /// Cancellation token.
+ ///
+ /// A task resolving to the raw series points in ascending capture order; empty when
+ /// the series has no samples in the window.
+ ///
+ Task> GetRawSeriesAsync(
+ string source, string metric, string scope, string? scopeKey,
+ DateTime fromUtc, DateTime toUtc, CancellationToken cancellationToken = default);
+
+ ///
+ /// Bulk-deletes rows whose is strictly older
+ /// than (retention purge).
+ ///
+ /// UTC cut-off; rows captured before this instant are deleted.
+ /// Cancellation token.
+ /// A task resolving to the number of rows deleted.
+ Task PurgeOlderThanAsync(DateTime before, CancellationToken cancellationToken = default);
+}
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Kpi/KpiScopes.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Kpi/KpiScopes.cs
new file mode 100644
index 00000000..4104b523
--- /dev/null
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Kpi/KpiScopes.cs
@@ -0,0 +1,21 @@
+namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Kpi;
+
+///
+/// Canonical KPI scope discriminators (M6 "KPI History & Trends") — the value of
+/// . Each
+/// constant's value equals its name. The companion
+/// qualifies
+/// the scope: null for , the site id for ,
+/// the node name for .
+///
+public static class KpiScopes
+{
+ /// System-wide sample; ScopeKey is null.
+ public const string Global = "Global";
+
+ /// Per-site sample; ScopeKey is the site id.
+ public const string Site = "Site";
+
+ /// Per-node sample; ScopeKey is the node name.
+ public const string Node = "Node";
+}
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Kpi/KpiSeriesPoint.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Kpi/KpiSeriesPoint.cs
new file mode 100644
index 00000000..29f91cae
--- /dev/null
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Kpi/KpiSeriesPoint.cs
@@ -0,0 +1,11 @@
+namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Kpi;
+
+///
+/// A single bucketed point of a KPI series (M6 "KPI History & Trends") — the
+/// aggregated value of one series over the bucket starting at
+/// . Returned by the bucketed query path and the
+/// reusable trend chart.
+///
+/// UTC start of the time bucket this point covers.
+/// Aggregated value for the bucket — counts exact; ages as seconds.
+public readonly record struct KpiSeriesPoint(DateTime BucketStartUtc, double Value);
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Kpi/KpiSources.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Kpi/KpiSources.cs
new file mode 100644
index 00000000..f173f7cb
--- /dev/null
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Kpi/KpiSources.cs
@@ -0,0 +1,22 @@
+namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Kpi;
+
+///
+/// Canonical KPI source identifiers (M6 "KPI History & Trends") — the value of
+/// and the
+///
+/// each owning component reports. Each constant's value equals its name.
+///
+public static class KpiSources
+{
+ /// Notification Outbox (#21) delivery KPIs.
+ public const string NotificationOutbox = "NotificationOutbox";
+
+ /// Site Call Audit (#22) cached-call KPIs.
+ public const string SiteCallAudit = "SiteCallAudit";
+
+ /// Audit Log (#23) ingest / backlog KPIs.
+ public const string AuditLog = "AuditLog";
+
+ /// Site Health (#11) monitoring KPIs.
+ public const string SiteHealth = "SiteHealth";
+}
diff --git a/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Kpi/KpiSampleTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Kpi/KpiSampleTests.cs
new file mode 100644
index 00000000..1d02c8a1
--- /dev/null
+++ b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Kpi/KpiSampleTests.cs
@@ -0,0 +1,77 @@
+using ZB.MOM.WW.ScadaBridge.Commons.Entities.Kpi;
+using ZB.MOM.WW.ScadaBridge.Commons.Types.Kpi;
+
+namespace ZB.MOM.WW.ScadaBridge.Commons.Tests.Kpi;
+
+public class KpiSampleTests
+{
+ [Fact]
+ public void KpiSample_ConstructedWithRequiredMembers_RoundTripsValues()
+ {
+ var capturedAt = new DateTime(2026, 6, 15, 12, 30, 0, DateTimeKind.Utc);
+
+ var sample = new KpiSample
+ {
+ Id = 42,
+ Source = KpiSources.NotificationOutbox,
+ Metric = "QueueDepth",
+ Scope = KpiScopes.Global,
+ ScopeKey = null,
+ Value = 17,
+ CapturedAtUtc = capturedAt,
+ };
+
+ Assert.Equal(42, sample.Id);
+ Assert.Equal("NotificationOutbox", sample.Source);
+ Assert.Equal("QueueDepth", sample.Metric);
+ Assert.Equal("Global", sample.Scope);
+ Assert.Null(sample.ScopeKey);
+ Assert.Equal(17, sample.Value);
+ Assert.Equal(capturedAt, sample.CapturedAtUtc);
+ Assert.Equal(DateTimeKind.Utc, sample.CapturedAtUtc.Kind);
+ }
+
+ [Fact]
+ public void KpiSample_ScopedSample_CarriesScopeKey()
+ {
+ var sample = new KpiSample
+ {
+ Source = KpiSources.SiteHealth,
+ Metric = "OldestPendingAgeSeconds",
+ Scope = KpiScopes.Site,
+ ScopeKey = "site-a",
+ Value = 12.5,
+ CapturedAtUtc = DateTime.UtcNow,
+ };
+
+ Assert.Equal(KpiScopes.Site, sample.Scope);
+ Assert.Equal("site-a", sample.ScopeKey);
+ Assert.Equal(12.5, sample.Value);
+ }
+
+ [Fact]
+ public void KpiScopesAndSources_ConstantValues_EqualTheirNames()
+ {
+ Assert.Equal("NotificationOutbox", KpiSources.NotificationOutbox);
+ Assert.Equal("SiteCallAudit", KpiSources.SiteCallAudit);
+ Assert.Equal("AuditLog", KpiSources.AuditLog);
+ Assert.Equal("SiteHealth", KpiSources.SiteHealth);
+
+ Assert.Equal("Global", KpiScopes.Global);
+ Assert.Equal("Site", KpiScopes.Site);
+ Assert.Equal("Node", KpiScopes.Node);
+ }
+
+ [Fact]
+ public void KpiSeriesPoint_IsValueRecordStruct_WithComponentEquality()
+ {
+ var bucket = new DateTime(2026, 6, 15, 0, 0, 0, DateTimeKind.Utc);
+
+ var a = new KpiSeriesPoint(bucket, 3.0);
+ var b = new KpiSeriesPoint(bucket, 3.0);
+
+ Assert.Equal(bucket, a.BucketStartUtc);
+ Assert.Equal(3.0, a.Value);
+ Assert.Equal(a, b);
+ }
+}