darkplex-core/cortex/predict.py
Claudia 47f9703e3b
All checks were successful
Tests / test (push) Successful in 3s
feat: port needs, alert, summarize, anomaly, predict, monitor modules
2026-02-09 16:20:22 +01:00

229 lines
7.4 KiB
Python

#!/usr/bin/env python3
"""Predictive Actions — Pattern-based proactive suggestions.
Analyzes behavior patterns and predicts what user might need next.
Usage:
cortex predict [--learn] [--patterns] [--json]
"""
import argparse
import base64
import json
import re
import subprocess
import sys
from datetime import datetime
from pathlib import Path
from cortex.config import cortex_home, memory_dir
def patterns_file():
return memory_dir() / "behavior-patterns.json"
def _load_patterns():
pf = patterns_file()
if pf.exists():
try:
return json.loads(pf.read_text())
except Exception:
pass
return {"timePatterns": {}, "sequences": {}, "recurring": [], "lastUpdated": None}
def _save_patterns(patterns):
pf = patterns_file()
pf.parent.mkdir(parents=True, exist_ok=True)
patterns["lastUpdated"] = datetime.now().isoformat()
pf.write_text(json.dumps(patterns, indent=2))
def fetch_events(hours: int = 168) -> list:
"""Fetch events for learning (default 1 week)."""
nats = str(Path.home() / "bin" / "nats")
cutoff_ms = int((datetime.now().timestamp() - hours * 3600) * 1000)
events = []
try:
r = subprocess.run([nats, "stream", "info", "openclaw-events", "--json"],
capture_output=True, text=True, timeout=10)
if r.returncode != 0:
return events
info = json.loads(r.stdout)
end_seq = info["state"]["last_seq"]
start_seq = max(info["state"]["first_seq"], end_seq - 10000)
step = max(1, (end_seq - start_seq) // 2000)
for seq in range(start_seq, end_seq + 1, step):
try:
r = subprocess.run(
[nats, "stream", "get", "openclaw-events", str(seq), "--json"],
capture_output=True, text=True, timeout=2,
)
if r.returncode != 0:
continue
msg = json.loads(r.stdout)
data = json.loads(base64.b64decode(msg["data"]).decode("utf-8"))
ts = data.get("timestamp") or data.get("timestampMs", 0)
if isinstance(ts, (int, float)) and ts > 1e12:
ts_s = ts / 1000
elif isinstance(ts, (int, float)):
ts_s = ts
else:
continue
if ts_s * 1000 > cutoff_ms:
events.append({
"time": datetime.fromtimestamp(ts_s),
"type": data.get("type", "unknown"),
"text": (data.get("payload", {}).get("data", {}).get("text", "") or "")[:200],
"tool": data.get("payload", {}).get("data", {}).get("name", ""),
"agent": data.get("agent", "main"),
})
except Exception:
continue
except Exception:
pass
return sorted(events, key=lambda e: e["time"])
def categorize_activity(event: dict) -> str:
text = event["text"].lower()
if any(w in text for w in ("email", "mail", "inbox")):
return "email"
if any(w in text for w in ("calendar", "meeting", "termin")):
return "calendar"
if any(w in text for w in ("git", "commit", "push")):
return "git"
if any(w in text for w in ("search", "web_search")):
return "search"
if any(w in text for w in ("mondo", "mygate", "fintech")):
return "mondo-gate"
if event["tool"] == "exec":
return "shell"
if event["tool"] in ("read", "write"):
return "files"
if "message" in event["type"]:
return "chat"
return "other"
def learn_patterns(events: list) -> dict:
patterns = _load_patterns()
patterns["timePatterns"] = {}
patterns["sequences"] = {}
last_activity = None
for event in events:
hour = event["time"].hour
dow = event["time"].weekday()
activity = categorize_activity(event)
key = f"{dow}-{hour}"
patterns["timePatterns"].setdefault(key, {})
patterns["timePatterns"][key][activity] = patterns["timePatterns"][key].get(activity, 0) + 1
if last_activity and last_activity != activity:
patterns["sequences"].setdefault(last_activity, {})
patterns["sequences"][last_activity][activity] = \
patterns["sequences"][last_activity].get(activity, 0) + 1
last_activity = activity
return patterns
def predict_actions(patterns: dict) -> list:
now = datetime.now()
key = f"{now.weekday()}-{now.hour}"
predictions = []
time_activities = patterns["timePatterns"].get(key, {})
for activity, count in sorted(time_activities.items(), key=lambda x: -x[1])[:3]:
if count >= 3:
predictions.append({
"type": "time-based",
"activity": activity,
"confidence": min(0.9, count / 10),
"reason": f"You often do this at this time",
})
return predictions
SUGGESTIONS = {
"email": "Check emails?",
"calendar": "Review calendar?",
"git": "Check git status?",
"search": "Need to research something?",
"mondo-gate": "Work on Mondo Gate?",
"shell": "Run system checks?",
"files": "Edit documentation or notes?",
"chat": "Check messages?",
}
def main():
parser = argparse.ArgumentParser(description="Predictive Actions")
parser.add_argument("--learn", action="store_true")
parser.add_argument("--patterns", action="store_true")
parser.add_argument("--json", action="store_true")
args = parser.parse_args()
if args.learn:
print("📚 Learning patterns from last 7 days...\n")
events = fetch_events(168)
print(f" Found {len(events)} events\n")
patterns = learn_patterns(events)
_save_patterns(patterns)
print(f"✅ Patterns learned!")
print(f" Time patterns: {len(patterns['timePatterns'])} time slots")
print(f" Sequences: {len(patterns['sequences'])} transitions")
return
if args.patterns:
patterns = _load_patterns()
if args.json:
print(json.dumps(patterns, indent=2, default=str))
else:
print("📊 Learned Patterns:\n")
now = datetime.now()
for h in range(8, 23):
key = f"{now.weekday()}-{h}"
acts = patterns["timePatterns"].get(key, {})
if acts:
top = ", ".join(f"{a}({c})" for a, c in sorted(acts.items(), key=lambda x: -x[1])[:2])
print(f" {h}:00 → {top}")
return
# Default: predict
patterns = _load_patterns()
if not patterns["lastUpdated"]:
print("⚠️ No patterns learned yet. Run with --learn first.")
return
predictions = predict_actions(patterns)
if args.json:
print(json.dumps(predictions, indent=2))
return
if not predictions:
print("🤔 No strong predictions for this time.")
return
print(f"📍 Now: {datetime.now().strftime('%H:%M')}\n")
print("Based on your patterns:\n")
for p in predictions:
conf = int(p["confidence"] * 100)
bar = "" * (conf // 10) + "" * (10 - conf // 10)
print(f" {bar} {conf}% {p['activity']}")
print(f" 💡 {SUGGESTIONS.get(p['activity'], p['activity'] + '?')}")
print(f" 📝 {p['reason']}\n")
if __name__ == "__main__":
main()