#!/usr/bin/env python3 """Create four forensic plots using only one NotePilot debug bundle.""" import json import sys from pathlib import Path def main(): if len(sys.argv) not in (2, 3): print('usage: plot_debug_bundle.py bundle.json [output-directory]', file=sys.stderr); return 2 bundle = json.loads(Path(sys.argv[1]).read_text()) out = Path(sys.argv[2]) if len(sys.argv) == 3 else Path(sys.argv[1]).with_suffix('') out.mkdir(parents=True, exist_ok=True) rows = bundle['diagnostics']['timeline'] t = [r['tMs'] / 1000 for r in rows] def vals(name): return [r.get(name) for r in rows] events = bundle['events'] def marks(kind): return [e['sessionTimeMs'] / 1000 for e in events if e.get('type') == kind] # Self-contained SVG avoids a plotting-package dependency in a fresh shell. def graph(name, title, series, vertical=()): width, height, pad = 1200, 460, 55 numeric = [v for _, values, _ in series for v in values if isinstance(v, (int, float))] lo, hi = (min(numeric), max(numeric)) if numeric else (0, 1) if lo == hi: lo -= 1; hi += 1 end = max(t) if t else 1 def xy(i, value): return (pad + (t[i] / end) * (width - 2 * pad), height - pad - ((value - lo) / (hi - lo)) * (height - 2 * pad)) paths = [] for label, values, color in series: points = [f'{x:.2f},{y:.2f}' for i, v in enumerate(values) if isinstance(v, (int, float)) for x, y in [xy(i, v)]] paths.append(f'{label}') lines = ''.join(f'' for x in vertical) svg = f'{title}{lines}{"".join(paths)}session seconds (range 0–{end:.2f}); value range {lo:.3g}–{hi:.3g}' (out / name).write_text(svg) attempts = [e['sessionTimeMs']/1000 for e in events if e.get('physicalAttemptId') is not None] graph('01-mic-level.svg', 'Microphone level / noise floor / attempts', [('mic dB', vals('db'), '#1769aa'), ('noise floor dB', vals('noiseFloorDb'), '#777')], attempts + marks('mic.gate')) graph('02-pitch.svg', 'Blind YIN pitch and expected score', [('blind MIDI', vals('blindMidi'), '#1769aa'), ('expected MIDI', vals('expectedMidi'), '#e68613')], attempts) graph('03-onset.svg', 'Onset feature and attack flags', [('onset ratio', vals('onsetValue'), '#6a3d9a')], [r['tMs']/1000 for r in rows if r.get('attack')]) graph('04-follower.svg', 'Follower posterior / WRONG / evidence', [('posterior', vals('posterior'), '#1769aa'), ('committed posterior', vals('committedPosterior'), '#e68613'), ('WRONG', vals('wrongProbability'), '#c44'), ('evidence nats', vals('evidenceNats'), '#6a3d9a')]) print(f'Wrote four plots to {out}'); return 0 if __name__ == '__main__': raise SystemExit(main())