# 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 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}")