# Compares the gate's raw.all wire rows with direct read-only SQL on the AVEVA Runtime DB (History, Full, UTC). import json, sys, os, subprocess, math from datetime import datetime, timezone rep = json.load(open(sys.argv[1])) def sql(q): env = dict(os.environ, SQLCMDPASSWORD=os.environ['HISTORIAN_SQL_PASSWORD']) out = subprocess.run(['sqlcmd', '-S', 'wonder-sql-vd03.zmr.zimmer.com,1433', '-U', os.environ['HISTORIAN_SQL_USER'], '-C', '-d', 'Runtime', '-h', '-1', '-W', '-s', '|', '-Q', 'SET NOCOUNT ON; ' + q], env=env, capture_output=True, text=True, timeout=120) if out.returncode: raise SystemExit(out.stderr + out.stdout) return [l.split('|') for l in out.stdout.splitlines() if l.strip()] def iso(s): # '2026-11-10 11:30:01.6200000' -> datetime return datetime.strptime(s[:23], '%Y-%m-%d %H:%M:%S.%f').replace(tzinfo=timezone.utc) def wts(s): return datetime.fromisoformat(s.replace('Z', '+00:00')) total = bad = 0 for c in rep['Cases']: if c['Name'] != 'raw.all' or c['Path'] != 'wire' or c['Tag'].startswith('OtOpcUa.'): continue start, end = c['Window'].split('/') s0, e0 = wts(start), wts(end) rows = sql(f"SELECT CONVERT(varchar(27), DateTime, 121), CONVERT(varchar(40), Value, 3), Quality, QualityDetail, OPCQuality " f"FROM dbo.History WHERE TagName = '{c['Tag']}' AND wwRetrievalMode = 'Full' AND wwTimeZone = 'UTC' " f"AND DateTime >= '{s0:%Y-%m-%d %H:%M:%S}' AND DateTime < '{e0:%Y-%m-%d %H:%M:%S}' ORDER BY DateTime") # AVEVA's synthetic boundary row (Quality 133) at the query start is not a stored sample (#687): OtOpcUa drops it. stored = [r for r in rows if not (r[2] == '133' and iso(r[0]) == s0)] wire = c['Rows'] mism = [] if len(stored) != len(wire): mism.append(f"count sql={len(stored)} wire={len(wire)}") for i, (r, w) in enumerate(zip(stored, wire)): t_ok = abs((iso(r[0]) - wts(w['Src'])).total_seconds()) < 0.0005 sv = None if r[1] == 'NULL' else float(r[1]) wv = None if w['V'] is None else float(w['V']) v_ok = (sv is None and wv is None) or (sv is not None and wv is not None and sv == wv) good = r[4] == '192' q_ok = (w['Status'] & 0xC0000000) == (0 if good else 0x80000000) if not (t_ok and v_ok and q_ok): mism.append(f"[{i}] sql={r} wire={w}"); break total += 1; bad += bool(mism) q = {} for r in stored: q[(r[2], r[3], r[4])] = q.get((r[2], r[3], r[4]), 0) + 1 print(f"{c['Tag']:<30} {start[:19]} sql_stored={len(stored)} (boundary rows dropped={len(rows)-len(stored)}) wire={len(wire)} " f"quality(Q,QD,OPC)={q} -> {'MATCH' if not mism else 'MISMATCH ' + '; '.join(mism)}") print(f"{total} tag-windows compared, {bad} mismatching")