diff --git a/utils/analyze_log.py b/utils/analyze_log.py index c52ae0f..17b938e 100644 --- a/utils/analyze_log.py +++ b/utils/analyze_log.py @@ -1,14 +1,16 @@ #!/usr/bin/env python3 -"""Offline analysis of a log/forecast pair produced by the BrewPi server. +"""Offline analysis of a log (and optional forecast) produced by the BrewPi server. Usage: python analyze_log.py [--log-dir ] + python analyze_log.py -Example: - python analyze_log.py 20260628T184903 Sud-0010 +The two-argument form loads logs/_.json and its paired +forecast file. The single-argument form accepts a direct path to any +log_*.json (e.g. a server-session log without a Sud name); the forecast panel +is shown only if a matching forecast_*.json exists alongside it. -Loads logs/_{sud_name}.json (log and forecast) and renders the same -two plots the PyQt GUI shows: the 3-panel realtime strip chart and the +Renders the 3-panel realtime strip chart and, when available, the forecast-vs-actual comparison. """ @@ -102,33 +104,42 @@ def plot_forecast(forecast, samples, name): def main(): parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument('date_time', help='Timestamp part of the filename, e.g. 20260628T184903') - parser.add_argument('sud_name', help='Sud name part of the filename, e.g. Sud-0010') + parser.add_argument('date_time_or_path', + help='Timestamp part of the filename (e.g. 20260628T184903) ' + 'or a direct path to a log_*.json file') + parser.add_argument('sud_name', nargs='?', default=None, + help='Sud name part of the filename (e.g. Sud-0010); ' + 'omit when passing a direct file path') default_log_dir = Path(__file__).parent.parent / 'logs' parser.add_argument('--log-dir', default=str(default_log_dir), help='Directory containing the log files (default: %(default)s)') args = parser.parse_args() - log_dir = Path(args.log_dir) - stem = '{}_{}'.format(args.date_time, args.sud_name) - log_path = log_dir / 'log_{}.json'.format(stem) - forecast_path = log_dir / 'forecast_{}.json'.format(stem) + if args.sud_name is None: + log_path = Path(args.date_time_or_path) + stem = log_path.stem[len('log_'):] if log_path.stem.startswith('log_') else log_path.stem + forecast_path = log_path.parent / 'forecast_{}.json'.format(stem) + else: + log_dir = Path(args.log_dir) + stem = '{}_{}'.format(args.date_time_or_path, args.sud_name) + log_path = log_dir / 'log_{}.json'.format(stem) + forecast_path = log_dir / 'forecast_{}.json'.format(stem) - for path in (log_path, forecast_path): - if not path.exists(): - print('Error: file not found: {}'.format(path), file=sys.stderr) - sys.exit(1) + if not log_path.exists(): + print('Error: file not found: {}'.format(log_path), file=sys.stderr) + sys.exit(1) with open(log_path) as f: log = json.load(f) - with open(forecast_path) as f: - forecast = json.load(f) name = log.get('Name', stem) samples = log['Samples'] plot_realtime(samples, name) - plot_forecast(forecast, samples, name) + if forecast_path.exists(): + with open(forecast_path) as f: + forecast = json.load(f) + plot_forecast(forecast, samples, name) plt.show()