Files
lmxopcua/scripts/validation/aveva-history-801/hgcli_raw_check.py
T
Joseph Doherty f607ee7502 test(historian): #801 real-AVEVA history gate over the SDK wire (env-gated)
AvevaGatewayHistoryLiveGateTests boots the server in-process, wires the production
Gateway backend (HistorianBackendRegistry -> GatewayHistorianDataSource) to a real
HistorianGateway and drives HistoryRead over a real SDK session: raw (all, paged,
reverse, bounds), modified, the ten served aggregates and seven refused ones, AtTime,
events (all, 1-event pages, reverse), a missing tag, HistoryServerCapabilities and
node metadata, and a HistoryUpdate that must be refused (sandbox-bound node only).
Every wire answer is compared with the backend's direct answer; the run is dumped as
JSON for cross-SDK and SQL comparison. Skips unless HISTGW_GATEWAY_ENDPOINT and
HISTGW_GATEWAY_APIKEY are set; read-only by construction.

scripts/validation/aveva-history-801: run/compare instructions, the SDK 1.5.378
baseline port patch (API spelling only), and the diff / SQL / aggregate / histgw-cli
comparison scripts.

Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
2026-11-12 10:02:21 -05:00

20 lines
1.5 KiB
Python

# Compares the gate's raw.all wire rows with HistorianGateway's own smoke CLI (`histgw-cli read-raw`, JSON lines),
# an independent gRPC client. Usage: hgcli_raw_check.py <aveva-gate-*.json> <dir with hgcli-*.jsonl>
import json, sys, glob, os
from datetime import datetime
rep = json.load(open(sys.argv[1]))
def ts(s): return datetime.fromisoformat(s.replace('Z', '+00:00').replace('0000000+', '+'))
for f in sorted(glob.glob(os.path.join(sys.argv[2], 'hgcli-*.jsonl'))):
rows = [json.loads(l) for l in open(f)]
tag, start = rows[0]['tag'], rows[0]['timestamp'][:19]
c = next(c for c in rep['Cases'] if c['Name'] == 'raw.all' and c['Path'] == 'wire' and c['Tag'] == tag and c['Window'].startswith(start))
w0, w1 = (ts(x) for x in c['Window'].split('/'))
# AVEVA's synthetic Quality-133 row at the query start is not a stored sample (#687); the gateway's end is inclusive,
# OtOpcUa's exclusive.
stored = [r for r in rows if not (r['quality'] == 133 and ts(r['timestamp']) == w0) and ts(r['timestamp']) < w1]
n = min(len(stored), len(c['Rows']))
ok = all(abs((ts(a['timestamp']) - ts(b['Src'])).total_seconds()) < 5e-4 and float(a['value']) == float(b['V'])
for a, b in zip(stored[:n], c['Rows'][:n]))
short = ' (histgw-cli stopped at the stored NULL: its JSON writer cannot emit NaN)' if len(stored) < len(c['Rows']) else ''
print(f"{tag:<16} {start} histgw-cli={len(stored)} wire={len(c['Rows'])} compared={n} {'MATCH' if ok else 'MISMATCH'}{short}")