#!/usr/bin/env python3 """ metrics - Track arbitrary metrics over time Log numeric values and see trends. """ import json import sys from datetime import datetime, timedelta from pathlib import Path from statistics import mean, stdev WORKSPACE = Path("/home/wdjones/.openclaw/workspace") METRICS_FILE = WORKSPACE / "data" / "metrics.json" def load_metrics() -> dict: """Load metrics data.""" METRICS_FILE.parent.mkdir(parents=True, exist_ok=True) if METRICS_FILE.exists(): with open(METRICS_FILE) as f: return json.load(f) return {'metrics': {}} def save_metrics(data: dict): """Save metrics data.""" with open(METRICS_FILE, 'w') as f: json.dump(data, f, indent=2) def define_metric(name: str, unit: str = '', description: str = ''): """Define a new metric to track.""" data = load_metrics() key = name.lower().replace(' ', '_') if key in data['metrics']: print(f"Metric already exists: {name}") return data['metrics'][key] = { 'name': name, 'unit': unit, 'description': description, 'entries': [], 'created': datetime.now().isoformat(), } save_metrics(data) print(f"šŸ“Š Created metric: {name}") def log_value(name: str, value: float, note: str = None): """Log a value for a metric.""" data = load_metrics() key = name.lower().replace(' ', '_') # Find metric by partial match matches = [k for k in data['metrics'] if key in k] if not matches: print(f"Metric not found: {name}") print("Create with: metrics define ") return key = matches[0] metric = data['metrics'][key] entry = { 'value': value, 'timestamp': datetime.now().isoformat(), 'note': note, } metric['entries'].append(entry) save_metrics(data) unit = metric.get('unit', '') print(f"šŸ“ˆ {metric['name']}: {value}{unit}") def show_metric(name: str, days: int = 7): """Show metric details and trend.""" data = load_metrics() key = name.lower().replace(' ', '_') matches = [k for k in data['metrics'] if key in k] if not matches: print(f"Metric not found: {name}") return key = matches[0] metric = data['metrics'][key] entries = metric['entries'] print(f"\nšŸ“Š {metric['name']}") if metric.get('description'): print(f" {metric['description']}") print("=" * 40) if not entries: print("No data yet") return # Filter to date range cutoff = datetime.now() - timedelta(days=days) recent = [e for e in entries if datetime.fromisoformat(e['timestamp']) > cutoff] if not recent: print(f"No data in last {days} days") return values = [e['value'] for e in recent] unit = metric.get('unit', '') # Stats avg = mean(values) latest = values[-1] high = max(values) low = min(values) print(f"\n Last {days} days ({len(recent)} entries):") print(f" Latest: {latest}{unit}") print(f" Average: {avg:.1f}{unit}") print(f" High: {high}{unit}") print(f" Low: {low}{unit}") # Trend if len(values) >= 2: first_half = mean(values[:len(values)//2]) second_half = mean(values[len(values)//2:]) if second_half > first_half * 1.05: print(f" Trend: šŸ“ˆ Up") elif second_half < first_half * 0.95: print(f" Trend: šŸ“‰ Down") else: print(f" Trend: āž”ļø Stable") # Recent entries print(f"\n Recent:") for e in recent[-5:]: date = e['timestamp'][:10] note = f" ({e['note']})" if e.get('note') else "" print(f" {date}: {e['value']}{unit}{note}") print() def list_metrics(): """List all metrics.""" data = load_metrics() if not data['metrics']: print("No metrics defined yet") print("Create with: metrics define [unit] [description]") return print(f"\nšŸ“Š Metrics ({len(data['metrics'])})") print("=" * 40) for key, metric in data['metrics'].items(): entries = len(metric['entries']) unit = metric.get('unit', '') if entries > 0: latest = metric['entries'][-1]['value'] print(f" {metric['name']}: {latest}{unit} ({entries} entries)") else: print(f" {metric['name']}: (no data)") print() def main(): if len(sys.argv) < 2: print("Usage:") print(" metrics define [unit] [description]") print(" metrics log [note]") print(" metrics show [days]") print(" metrics list") print("") print("Examples:") print(" metrics define weight kg") print(" metrics log weight 75.5") print(" metrics show weight 30") list_metrics() return cmd = sys.argv[1] if cmd == 'define' and len(sys.argv) > 2: name = sys.argv[2] unit = sys.argv[3] if len(sys.argv) > 3 else '' desc = ' '.join(sys.argv[4:]) if len(sys.argv) > 4 else '' define_metric(name, unit, desc) elif cmd == 'log' and len(sys.argv) > 3: name = sys.argv[2] value = float(sys.argv[3]) note = ' '.join(sys.argv[4:]) if len(sys.argv) > 4 else None log_value(name, value, note) elif cmd == 'show' and len(sys.argv) > 2: name = sys.argv[2] days = int(sys.argv[3]) if len(sys.argv) > 3 else 7 show_metric(name, days) elif cmd == 'list': list_metrics() else: print("Unknown command. Run 'metrics' for help.") if __name__ == "__main__": main()