diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway/Mapping/EventMapper.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway/Mapping/EventMapper.cs
new file mode 100644
index 00000000..6aaadca8
--- /dev/null
+++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway/Mapping/EventMapper.cs
@@ -0,0 +1,67 @@
+using System.Globalization;
+using ZB.MOM.WW.HistorianGateway.Contracts.Grpc;
+using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
+
+namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Mapping;
+
+///
+/// Maps a gateway wire event () onto the driver-agnostic
+/// consumed by the Server's HistoryReadEvents path.
+///
+internal static class EventMapper
+{
+ /// OPC UA severity range (Part 9): 1 (lowest) … 1000 (highest).
+ private const ushort MinSeverity = 1;
+ private const ushort MaxSeverity = 1000;
+
+ /// Maps a single gateway event to a historical event.
+ /// The gateway wire event.
+ /// The driver-agnostic historical event.
+ public static HistoricalEvent ToHistoricalEvent(HistorianEvent historianEvent)
+ {
+ // Message: prefer the "Message" property, else fall back to the event Type (best-effort
+ // render); never null-crash on a missing property.
+ string? message;
+ if (historianEvent.Properties.TryGetValue("Message", out var m) && !string.IsNullOrEmpty(m))
+ message = m;
+ else
+ message = string.IsNullOrEmpty(historianEvent.Type) ? null : historianEvent.Type;
+
+ return new HistoricalEvent(
+ EventId: historianEvent.Id,
+ SourceName: string.IsNullOrEmpty(historianEvent.SourceName) ? null : historianEvent.SourceName,
+ EventTimeUtc: historianEvent.EventTime?.ToDateTime() ?? default, // Utc kind
+ ReceivedTimeUtc: historianEvent.ReceivedTime?.ToDateTime() ?? default, // Utc kind
+ Message: message,
+ Severity: ParseSeverity(historianEvent.Properties));
+ }
+
+ /// Maps a batch of gateway events to historical events, in order.
+ /// The gateway wire events.
+ /// The driver-agnostic historical events.
+ public static IReadOnlyList ToHistoricalEvents(IEnumerable events)
+ {
+ var result = new List();
+ foreach (var historianEvent in events)
+ result.Add(ToHistoricalEvent(historianEvent));
+ return result;
+ }
+
+ ///
+ /// Parses an OPC UA severity from the "Severity" property (else "Priority"), clamped to
+ /// [1, 1000]. Missing or unparseable values default to the minimum severity (1).
+ ///
+ private static ushort ParseSeverity(IDictionary properties)
+ {
+ string? raw = null;
+ if (properties.TryGetValue("Severity", out var severity))
+ raw = severity;
+ else if (properties.TryGetValue("Priority", out var priority))
+ raw = priority;
+
+ if (int.TryParse(raw, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
+ return (ushort)Math.Clamp(value, MinSeverity, MaxSeverity);
+
+ return MinSeverity;
+ }
+}
diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests/Mapping/EventMapperTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests/Mapping/EventMapperTests.cs
new file mode 100644
index 00000000..de0f2cdd
--- /dev/null
+++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests/Mapping/EventMapperTests.cs
@@ -0,0 +1,40 @@
+using Google.Protobuf.WellKnownTypes;
+using Xunit;
+using ZB.MOM.WW.HistorianGateway.Contracts.Grpc;
+using ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Mapping;
+
+namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests.Mapping;
+
+public sealed class EventMapperTests
+{
+ [Fact]
+ public void Maps_core_fields_and_times()
+ {
+ var e = new HistorianEvent { Id = "E1", SourceName = "Pump1",
+ EventTime = Ts(2026, 1, 1, 0, 0, 0), ReceivedTime = Ts(2026, 1, 1, 0, 0, 5) };
+ e.Properties["Message"] = "High temp";
+ e.Properties["Severity"] = "700";
+ var h = EventMapper.ToHistoricalEvent(e);
+ Assert.Equal("E1", h.EventId);
+ Assert.Equal("Pump1", h.SourceName);
+ Assert.Equal("High temp", h.Message);
+ Assert.Equal((ushort)700, h.Severity);
+ Assert.Equal(DateTimeKind.Utc, h.EventTimeUtc.Kind);
+ }
+
+ [Theory]
+ [InlineData("Priority", "999", 999)]
+ [InlineData("Severity", "0", 1)] // clamp to OPC UA min 1
+ [InlineData("Severity", "5000", 1000)] // clamp to OPC UA max 1000
+ [InlineData(null, null, 1)] // missing → default min severity
+ public void Severity_parsed_and_clamped(string? key, string? val, int expected)
+ {
+ var e = new HistorianEvent { Id = "E", EventTime = Ts(2026, 1, 1, 0, 0, 0), ReceivedTime = Ts(2026, 1, 1, 0, 0, 0) };
+ if (key is not null) e.Properties[key] = val!;
+ Assert.Equal((ushort)expected, EventMapper.ToHistoricalEvent(e).Severity);
+ }
+
+ // Ts(...) builds a Google.Protobuf.WellKnownTypes.Timestamp from UTC parts.
+ private static Timestamp Ts(int y, int mo, int d, int h, int mi, int s)
+ => Timestamp.FromDateTime(new DateTime(y, mo, d, h, mi, s, DateTimeKind.Utc));
+}