# Independent recomputation of the simple per-interval aggregates from the wire raw rows (Good samples only, [t, t+60s)). import json, sys, math from datetime import datetime, timedelta rep = json.load(open(sys.argv[1])); C = rep['Cases'] def ts(s): return datetime.fromisoformat(s.replace('Z', '+00:00')) def get(name, tag, win): return next(c for c in C if c['Name'] == name and c['Tag'] == tag and c['Window'] == win and c['Path'] == 'wire') checked = bad = 0 for raw in [c for c in C if c['Name'] == 'raw.all' and c['Path'] == 'wire' and not c['Tag'].startswith('OtOpcUa.')]: tag, win = raw['Tag'], raw['Window'] samples = [(ts(r['Src']), float(r['V'])) for r in raw['Rows'] if r['V'] is not None and r['Status'] & 0xC0000000 == 0] nongood = [ts(r['Src']) for r in raw['Rows'] if r['V'] is None or r['Status'] & 0xC0000000 != 0] for agg in ['Count', 'Minimum', 'Maximum', 'Average', 'Range', 'Start', 'End', 'Delta']: p = get(f'processed.{agg}', tag, win) for row in p['Rows']: w0 = ts(win.split('/')[0]); k = int((ts(row['Src']) - w0).total_seconds() // 60) t0 = w0 + timedelta(seconds=60 * k); t1 = t0 + timedelta(seconds=60) # Start/End carry the sample's own time xs = [v for (t, v) in samples if t0 <= t < t1] if not xs or any(t0 <= t < t1 for t in nongood): continue # empty / outage intervals: status semantics are compared against SDK1, not recomputed here exp = {'Count': len(xs), 'Minimum': min(xs), 'Maximum': max(xs), 'Average': sum(xs) / len(xs), 'Range': max(xs) - min(xs), 'Start': xs[0], 'End': xs[-1], 'Delta': xs[-1] - xs[0]}[agg] got = float(str(row['V']).split(':')[-1]) checked += 1 if not math.isclose(got, exp, rel_tol=1e-12, abs_tol=1e-9): bad += 1; print('MISMATCH', tag, win[:19], agg, row, 'expected', exp) print(f'{checked} non-empty interval values recomputed from raw, {bad} mismatching')