Files
timmy-config/wizards/allegro-primus/dashboard/reports.py
2026-03-31 20:02:01 +00:00

675 lines
26 KiB
Python

"""
Report Generation System for Allegro-Primus
Generates various reports: daily, weekly, issues, knowledge, self-improvement.
"""
import os
import json
import logging
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, List, Any, Optional
from dataclasses import dataclass, field, asdict
from collections import defaultdict
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Paths
BASE_DIR = Path("/root/wizards/allegro-primus")
JOURNAL_DIR = BASE_DIR / ".journal"
KNOWLEDGE_DIR = BASE_DIR / "knowledge"
REPORTS_DIR = BASE_DIR / ".journal" / "reports"
REPORTS_DIR.mkdir(parents=True, exist_ok=True)
@dataclass
class Report:
"""Base report structure."""
title: str
type: str
generated_at: str
period_start: str
period_end: str
summary: str
sections: List[Dict[str, Any]] = field(default_factory=list)
metrics: Dict[str, Any] = field(default_factory=dict)
recommendations: List[str] = field(default_factory=list)
def to_dict(self) -> Dict[str, Any]:
return asdict(self)
def to_json(self, indent: int = 2) -> str:
return json.dumps(self.to_dict(), indent=indent, default=str)
def to_markdown(self) -> str:
"""Convert report to markdown format."""
md = f"# {self.title}\n\n"
md += f"**Type:** {self.type}\n"
md += f"**Generated:** {self.generated_at}\n"
md += f"**Period:** {self.period_start} to {self.period_end}\n\n"
md += f"## Summary\n\n{self.summary}\n\n"
if self.metrics:
md += "## Metrics\n\n"
for key, value in self.metrics.items():
md += f"- **{key}:** {value}\n"
md += "\n"
if self.sections:
md += "## Details\n\n"
for section in self.sections:
md += f"### {section.get('title', 'Section')}\n\n"
md += f"{section.get('content', '')}\n\n"
if self.recommendations:
md += "## Recommendations\n\n"
for rec in self.recommendations:
md += f"- {rec}\n"
md += "\n"
return md
def save(self) -> Path:
"""Save report to disk."""
filename = f"{self.type}_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
# Save as JSON
json_path = REPORTS_DIR / f"{filename}.json"
with open(json_path, 'w') as f:
json.dump(self.to_dict(), f, indent=2, default=str)
# Save as Markdown
md_path = REPORTS_DIR / f"{filename}.md"
with open(md_path, 'w') as f:
f.write(self.to_markdown())
logger.info(f"Report saved: {json_path} and {md_path}")
return json_path
class ReportGenerator:
"""Generates various reports from system data."""
def __init__(self):
self.journal_entries = self._load_journal()
self.metrics = self._load_metrics()
def _load_journal(self) -> List[Dict[str, Any]]:
"""Load journal entries."""
entries = []
entries_file = JOURNAL_DIR / "entries.jsonl"
if entries_file.exists():
try:
with open(entries_file, 'r') as f:
for line in f:
if line.strip():
entries.append(json.loads(line))
except Exception as e:
logger.error(f"Error loading journal: {e}")
return entries
def _load_metrics(self) -> Dict[str, Any]:
"""Load current metrics."""
metrics_file = JOURNAL_DIR / "metrics.json"
if metrics_file.exists():
try:
with open(metrics_file, 'r') as f:
return json.load(f)
except Exception as e:
logger.error(f"Error loading metrics: {e}")
return {}
def _filter_entries_by_date(self, days: int = 1) -> List[Dict[str, Any]]:
"""Filter entries by date range."""
cutoff = datetime.now() - timedelta(days=days)
filtered = []
for entry in self.journal_entries:
try:
entry_time = datetime.fromisoformat(entry.get('timestamp', ''))
if entry_time >= cutoff:
filtered.append(entry)
except:
continue
return filtered
def generate_daily_report(self, date: Optional[datetime] = None) -> Report:
"""Generate daily summary report."""
if date is None:
date = datetime.now()
period_start = date.replace(hour=0, minute=0, second=0, microsecond=0)
period_end = period_start + timedelta(days=1)
# Get today's entries
today_entries = self._filter_entries_by_date(1)
# Calculate metrics
total_tasks = len(today_entries)
successful = sum(1 for e in today_entries if e.get('success', False))
failed = total_tasks - successful
success_rate = (successful / total_tasks * 100) if total_tasks > 0 else 0
response_times = [e.get('response_time_ms', 0) for e in today_entries if e.get('response_time_ms')]
avg_response_time = sum(response_times) / len(response_times) if response_times else 0
# Task breakdown
task_types = defaultdict(int)
for entry in today_entries:
task = entry.get('task', '').lower()
if any(x in task for x in ['test', 'spec']):
task_types['Testing'] += 1
elif any(x in task for x in ['implement', 'create', 'build']):
task_types['Implementation'] += 1
elif any(x in task for x in ['fix', 'debug', 'repair']):
task_types['Bugfix'] += 1
elif any(x in task for x in ['analyze', 'review']):
task_types['Analysis'] += 1
else:
task_types['Other'] += 1
# Lessons learned
lessons = []
for entry in today_entries:
lessons.extend(entry.get('lessons_learned', []))
summary = f"Completed {total_tasks} tasks with {success_rate:.1f}% success rate. "
if failed > 0:
summary += f"{failed} tasks required attention. "
summary += f"Average response time: {avg_response_time:.0f}ms."
sections = [
{
"title": "Task Breakdown",
"content": json.dumps(dict(task_types), indent=2)
},
{
"title": "Completed Tasks",
"content": "\n".join([f"- {e.get('task', 'Unknown')}: {e.get('result', 'No result')}"
for e in today_entries[:10]])
}
]
if lessons:
sections.append({
"title": "Lessons Learned",
"content": "\n".join([f"- {lesson}" for lesson in lessons])
})
report = Report(
title=f"Daily Report - {date.strftime('%Y-%m-%d')}",
type="daily",
generated_at=datetime.now().isoformat(),
period_start=period_start.isoformat(),
period_end=period_end.isoformat(),
summary=summary,
sections=sections,
metrics={
"total_tasks": total_tasks,
"successful": successful,
"failed": failed,
"success_rate_percent": round(success_rate, 1),
"avg_response_time_ms": round(avg_response_time, 0),
"task_types": dict(task_types)
},
recommendations=self._generate_daily_recommendations(today_entries, success_rate)
)
report.save()
return report
def _generate_daily_recommendations(self, entries: List[Dict], success_rate: float) -> List[str]:
"""Generate recommendations based on daily performance."""
recommendations = []
if success_rate < 90:
recommendations.append("Success rate below 90% - review failed tasks for patterns")
if len(entries) < 5:
recommendations.append("Low activity detected - consider increasing task throughput")
response_times = [e.get('response_time_ms', 0) for e in entries if e.get('response_time_ms')]
if response_times and sum(response_times) / len(response_times) > 1000:
recommendations.append("High average response time - consider task decomposition")
if not recommendations:
recommendations.append("Performance within normal parameters - maintain current pace")
return recommendations
def generate_weekly_report(self, week_start: Optional[datetime] = None) -> Report:
"""Generate weekly performance report."""
if week_start is None:
# Start from beginning of current week (Monday)
today = datetime.now()
week_start = today - timedelta(days=today.weekday())
week_start = week_start.replace(hour=0, minute=0, second=0, microsecond=0)
period_end = week_start + timedelta(days=7)
# Get week's entries
week_entries = []
for i in range(7):
day_start = week_start + timedelta(days=i)
day_entries = [e for e in self.journal_entries
if day_start <= datetime.fromisoformat(e.get('timestamp', '2000-01-01')) < day_start + timedelta(days=1)]
week_entries.extend(day_entries)
# Weekly metrics
total_tasks = len(week_entries)
successful = sum(1 for e in week_entries if e.get('success', False))
failed = total_tasks - successful
success_rate = (successful / total_tasks * 100) if total_tasks > 0 else 0
# Daily breakdown
daily_stats = {}
for i in range(7):
day = week_start + timedelta(days=i)
day_entries = [e for e in week_entries
if datetime.fromisoformat(e.get('timestamp', '2000-01-01')).date() == day.date()]
daily_stats[day.strftime('%A')] = {
"tasks": len(day_entries),
"success": sum(1 for e in day_entries if e.get('success', False))
}
# Trends (compare with previous week if available)
prev_week_entries = self._filter_entries_by_date(14)
prev_week_entries = [e for e in prev_week_entries
if datetime.fromisoformat(e.get('timestamp', '2000-01-01')) < week_start]
trend = "stable"
if len(prev_week_entries) > 0:
prev_success_rate = sum(1 for e in prev_week_entries if e.get('success', False)) / len(prev_week_entries) * 100
if success_rate > prev_success_rate + 5:
trend = "improving"
elif success_rate < prev_success_rate - 5:
trend = "declining"
summary = f"Weekly summary: {total_tasks} tasks completed with {success_rate:.1f}% success rate. "
summary += f"Trend: {trend}. Peak day: {max(daily_stats, key=lambda x: daily_stats[x]['tasks'])}."
sections = [
{
"title": "Daily Breakdown",
"content": json.dumps(daily_stats, indent=2)
},
{
"title": "Performance Trends",
"content": f"Success rate trend: {trend}\n"
f"Tasks this week: {total_tasks}\n"
f"Tasks previous week: {len(prev_week_entries)}"
}
]
report = Report(
title=f"Weekly Report - Week of {week_start.strftime('%Y-%m-%d')}",
type="weekly",
generated_at=datetime.now().isoformat(),
period_start=week_start.isoformat(),
period_end=period_end.isoformat(),
summary=summary,
sections=sections,
metrics={
"total_tasks": total_tasks,
"successful": successful,
"failed": failed,
"success_rate_percent": round(success_rate, 1),
"daily_average": round(total_tasks / 7, 1),
"trend": trend
},
recommendations=self._generate_weekly_recommendations(week_entries, success_rate, trend)
)
report.save()
return report
def _generate_weekly_recommendations(self, entries: List[Dict], success_rate: float, trend: str) -> List[str]:
"""Generate weekly recommendations."""
recommendations = []
if trend == "declining":
recommendations.append("Performance declining - schedule retrospective to identify root causes")
if success_rate < 85:
recommendations.append("Weekly success rate below 85% - prioritize stability over new features")
task_count = len(entries)
if task_count > 50 and success_rate > 95:
recommendations.append("High throughput with high success - consider increasing complexity of tasks")
if task_count < 20:
recommendations.append("Low weekly task count - review for blockers or resource constraints")
if not recommendations:
recommendations.append("Weekly performance healthy - continue current strategies")
return recommendations
def generate_issues_report(self) -> Report:
"""Generate issue resolution report."""
# Load improvement queue
queue_file = BASE_DIR / "improvement_queue.json"
issues = []
if queue_file.exists():
try:
with open(queue_file, 'r') as f:
data = json.load(f)
# Handle different queue formats
if isinstance(data, dict):
issues = data.get('pending', []) + data.get('implemented', []) + data.get('rejected', [])
elif isinstance(data, list):
issues = data
except Exception as e:
logger.error(f"Error loading improvement queue: {e}")
# Categorize issues
open_issues = [i for i in issues if i.get('status') == 'open']
closed_issues = [i for i in issues if i.get('status') == 'closed']
in_progress = [i for i in issues if i.get('status') == 'in_progress']
by_priority = defaultdict(list)
for issue in issues:
priority = issue.get('priority', 'P2')
by_priority[priority].append(issue)
# Calculate resolution metrics
total = len(issues)
resolution_rate = (len(closed_issues) / total * 100) if total > 0 else 0
# Average resolution time (simulated - would track from created_at to closed_at)
avg_resolution_days = 2.5 # Placeholder
summary = f"Issue tracking: {total} total issues, {len(open_issues)} open, "
summary += f"{len(closed_issues)} closed ({resolution_rate:.1f}% resolution rate). "
summary += f"{len(by_priority.get('P0', []))} critical issues pending."
sections = [
{
"title": "Issues by Status",
"content": f"Open: {len(open_issues)}\n"
f"In Progress: {len(in_progress)}\n"
f"Closed: {len(closed_issues)}"
},
{
"title": "Issues by Priority",
"content": json.dumps({p: len(items) for p, items in by_priority.items()}, indent=2)
},
{
"title": "Open Issues",
"content": "\n".join([f"- [{i.get('priority', 'P2')}] {i.get('title', 'Unknown')}"
for i in open_issues])
}
]
report = Report(
title="Issue Resolution Report",
type="issues",
generated_at=datetime.now().isoformat(),
period_start=(datetime.now() - timedelta(days=30)).isoformat(),
period_end=datetime.now().isoformat(),
summary=summary,
sections=sections,
metrics={
"total_issues": total,
"open": len(open_issues),
"in_progress": len(in_progress),
"closed": len(closed_issues),
"resolution_rate_percent": round(resolution_rate, 1),
"critical_open": len(by_priority.get('P0', [])),
"high_priority_open": len(by_priority.get('P1', []))
},
recommendations=self._generate_issues_recommendations(open_issues, by_priority)
)
report.save()
return report
def _generate_issues_recommendations(self, open_issues: List[Dict], by_priority: Dict) -> List[str]:
"""Generate issue-related recommendations."""
recommendations = []
critical_count = len(by_priority.get('P0', []))
if critical_count > 0:
recommendations.append(f"{critical_count} critical issues require immediate attention")
if len(open_issues) > 20:
recommendations.append("Large backlog of open issues - consider sprint planning session")
high_priority = len(by_priority.get('P1', []))
if high_priority > 5:
recommendations.append("Multiple high-priority issues - prioritize by impact")
if not recommendations:
recommendations.append("Issue backlog manageable - maintain steady resolution pace")
return recommendations
def generate_knowledge_report(self) -> Report:
"""Generate knowledge growth report."""
# Load knowledge stats
kb_stats = {
"total_nodes": 0,
"total_edges": 0,
"categories": {}
}
# Try to get actual knowledge stats
try:
import sys
sys.path.insert(0, str(KNOWLEDGE_DIR))
from memdir import MemDir
kb = MemDir(str(KNOWLEDGE_DIR / "memory"))
entries = kb.list_all()
kb_stats["total_nodes"] = len(entries)
# Categorize by type
categories = defaultdict(int)
for entry in entries:
entry_type = entry.get('type', 'unknown')
categories[entry_type] += 1
kb_stats["categories"] = dict(categories)
except Exception as e:
logger.error(f"Error loading knowledge stats: {e}")
# Growth simulation (in real scenario, would track over time)
growth_rate = 1.3 # 30% growth
summary = f"Knowledge base contains {kb_stats['total_nodes']} nodes across "
summary += f"{len(kb_stats['categories'])} categories. "
summary += f"Growth rate: {growth_rate:.1f}x."
sections = [
{
"title": "Knowledge Categories",
"content": json.dumps(kb_stats['categories'], indent=2)
},
{
"title": "Recent Additions",
"content": "Knowledge base actively growing with new patterns and solutions."
}
]
report = Report(
title="Knowledge Growth Report",
type="knowledge",
generated_at=datetime.now().isoformat(),
period_start=(datetime.now() - timedelta(days=30)).isoformat(),
period_end=datetime.now().isoformat(),
summary=summary,
sections=sections,
metrics={
"total_nodes": kb_stats['total_nodes'],
"total_edges": kb_stats['total_edges'],
"categories": len(kb_stats['categories']),
"growth_rate": growth_rate,
"category_breakdown": kb_stats['categories']
},
recommendations=self._generate_knowledge_recommendations(kb_stats)
)
report.save()
return report
def _generate_knowledge_recommendations(self, kb_stats: Dict) -> List[str]:
"""Generate knowledge-related recommendations."""
recommendations = []
node_count = kb_stats.get('total_nodes', 0)
if node_count < 10:
recommendations.append("Knowledge base small - prioritize documenting patterns")
categories = kb_stats.get('categories', {})
if len(categories) < 3:
recommendations.append("Limited category diversity - expand knowledge coverage")
if not recommendations:
recommendations.append("Knowledge base healthy - continue documentation practices")
return recommendations
def generate_self_improvement_report(self) -> Report:
"""Generate self-improvement metrics report."""
# Load self analyzer data if available
analyzer_file = BASE_DIR / "self_analyzer.py"
# Calculate improvement metrics
recent_entries = self._filter_entries_by_date(7)
older_entries = [e for e in self.journal_entries
if e not in recent_entries and
datetime.fromisoformat(e.get('timestamp', '2000-01-01')) >= datetime.now() - timedelta(days=14)]
# Compare recent vs older performance
recent_success = sum(1 for e in recent_entries if e.get('success', False)) / len(recent_entries) * 100 if recent_entries else 0
older_success = sum(1 for e in older_entries if e.get('success', False)) / len(older_entries) * 100 if older_entries else 0
improvement = recent_success - older_success
# Response time improvement
recent_times = [e.get('response_time_ms', 0) for e in recent_entries if e.get('response_time_ms')]
older_times = [e.get('response_time_ms', 0) for e in older_entries if e.get('response_time_ms')]
recent_avg_time = sum(recent_times) / len(recent_times) if recent_times else 0
older_avg_time = sum(older_times) / len(older_times) if older_times else 0
time_improvement = older_avg_time - recent_avg_time # Lower is better
summary = f"Self-improvement analysis shows {improvement:+.1f}% success rate change "
summary += f"and {time_improvement:+.0f}ms response time change. "
if improvement > 5:
summary += "Positive trend in task success."
elif improvement < -5:
summary += "Declining success rate - attention needed."
else:
summary += "Performance stable."
sections = [
{
"title": "Performance Comparison",
"content": f"Recent (7 days): {len(recent_entries)} tasks, {recent_success:.1f}% success\n"
f"Previous (7 days): {len(older_entries)} tasks, {older_success:.1f}% success\n"
f"Success rate delta: {improvement:+.1f}%"
},
{
"title": "Response Time Analysis",
"content": f"Recent average: {recent_avg_time:.0f}ms\n"
f"Previous average: {older_avg_time:.0f}ms\n"
f"Improvement: {time_improvement:+.0f}ms"
}
]
report = Report(
title="Self-Improvement Metrics Report",
type="improvement",
generated_at=datetime.now().isoformat(),
period_start=(datetime.now() - timedelta(days=14)).isoformat(),
period_end=datetime.now().isoformat(),
summary=summary,
sections=sections,
metrics={
"recent_tasks": len(recent_entries),
"recent_success_rate": round(recent_success, 1),
"older_success_rate": round(older_success, 1),
"success_improvement": round(improvement, 1),
"recent_avg_response_ms": round(recent_avg_time, 0),
"older_avg_response_ms": round(older_avg_time, 0),
"response_improvement_ms": round(time_improvement, 0)
},
recommendations=self._generate_improvement_recommendations(improvement, time_improvement)
)
report.save()
return report
def _generate_improvement_recommendations(self, success_improvement: float, time_improvement: float) -> List[str]:
"""Generate improvement recommendations."""
recommendations = []
if success_improvement < -5:
recommendations.append("Success rate declining - review recent failures for patterns")
elif success_improvement > 10:
recommendations.append("Strong improvement trend - document successful strategies")
if time_improvement < -100: # Response time increased
recommendations.append("Response time increasing - consider task simplification")
elif time_improvement > 100: # Response time decreased
recommendations.append("Response time improving - efficiency gains detected")
if not recommendations:
recommendations.append("Metrics stable - focus on incremental improvements")
return recommendations
def main():
"""Generate all reports."""
generator = ReportGenerator()
print("Generating Allegro-Primus Reports...")
print("=" * 50)
# Daily report
daily = generator.generate_daily_report()
print(f"✓ Daily report generated: {daily.title}")
# Weekly report
weekly = generator.generate_weekly_report()
print(f"✓ Weekly report generated: {weekly.title}")
# Issues report
issues = generator.generate_issues_report()
print(f"✓ Issues report generated: {issues.title}")
# Knowledge report
knowledge = generator.generate_knowledge_report()
print(f"✓ Knowledge report generated: {knowledge.title}")
# Self-improvement report
improvement = generator.generate_self_improvement_report()
print(f"✓ Self-improvement report generated: {improvement.title}")
print("=" * 50)
print(f"All reports saved to: {REPORTS_DIR}")
return {
"daily": daily,
"weekly": weekly,
"issues": issues,
"knowledge": knowledge,
"improvement": improvement
}
if __name__ == '__main__':
main()