diff --git a/docs/Historian.md b/docs/Historian.md
index 0faa5f86..e7ab23ec 100644
--- a/docs/Historian.md
+++ b/docs/Historian.md
@@ -787,7 +787,7 @@ and evicts its shared historian session (see the HistorianGateway issue text in
(histsdk evidence: `Quality = 133`, `OpcQuality = 192`) — the value in effect there, not a stored sample. Starting 1 ms early puts
that row outside every bucket. A pre-window row only proves "the tag has earlier data" (start-of-data Partial, Count's in-range
zero), and only when its **OPC quality is not Bad** — a "no earlier data" NULL row still carries a value on the wire (histsdk sets
- `NumericValue = 0` on every numeric row and `StringValue = ""` on a string one) but with OPC quality 0.
+ `NumericValue = 0` on every numeric row and `StringValue = ""` on a string one) but with a Bad OPC quality (captured as 1, no-data; histsdk string-read-layout.md).
- **Earlier-data probe.** When the data range matters (every per-sample aggregate but `Average`) and no non-Bad pre-window row
arrived, a 2-row read `[1970-01-01, start)` asks whether the tag has data before the window. Normally this fires only for a tag
whose history really begins inside the window.
diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway/RawAggregateReader.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway/RawAggregateReader.cs
index f2e59d80..64738bca 100644
--- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway/RawAggregateReader.cs
+++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway/RawAggregateReader.cs
@@ -30,7 +30,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway;
/// (1 ms) BEFORE the window, so that row lands outside every bucket and can never be counted as a sample. A row
/// before the window counts only as evidence that the tag has earlier data, and only when its OPC quality is not
/// Bad — a "no earlier data" NULL row still arrives WITH a value (histsdk sets NumericValue = 0 on every
-/// numeric row, StringValue = "" on a string one) but with OPC quality 0.
+/// numeric row, StringValue = "" on a string one) but with a Bad OPC quality (captured: 1, no-data).
///
///
/// The data range. The start/end-of-data Partial rules and Count's in-range empty interval need the tag's
@@ -142,7 +142,7 @@ internal static class RawAggregateReader
// latest at or after its earlier edge. A row before the window (the AVEVA boundary row, or a stored sample in
// the margin) proves earlier data only when its OPC quality is not Bad: AVEVA's boundary row for a tag with no
// earlier data is a NULL row, which arrives WITH a value (histsdk sets NumericValue = 0 / StringValue "") and
- // OPC quality 0.
+ // a Bad OPC quality (captured: 1, no-data).
if (t >= plan.StartUtc || IsNotBadOnTheWire(raw))
{
if (t <= plan.EndUtc && (earliest is null || t < earliest)) earliest = t;
@@ -199,7 +199,7 @@ internal static class RawAggregateReader
/// 's fallback to the AVEVA quality field when opc_quality is 0, which
/// is also the OPC DA Bad value a NULL row carries.
///
- private static bool IsNotBadOnTheWire(HistorianSample raw) => raw.OpcQuality >= 64;
+ private static bool IsNotBadOnTheWire(HistorianSample raw) => (byte)raw.OpcQuality >= 64; // low byte, as SampleMapper.ToStatus reads it
private static HistoryReadLimitExceededException LimitExceeded(string tag, int maxWindowRows) =>
new(maxWindowRows,