Add decision journal, metrics, ideas, weather, standup - decide.py: Decision journal with outcomes and lessons - metrics.py: Track arbitrary metrics over time - ideas.py: Idea incubator with stages - weather.py: Quick weather via wttr.in - standup.py: Daily standup generator - 27 tools total now

This commit is contained in:
2026-01-30 23:55:37 -06:00
parent 9b7d7db55c
commit 3c9dc28852
12 changed files with 957 additions and 6 deletions

208
tools/metrics.py Executable file
View File

@ -0,0 +1,208 @@
#!/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 <name>")
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 <name> [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 <name> [unit] [description]")
print(" metrics log <name> <value> [note]")
print(" metrics show <name> [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()