mirror of
https://github.com/Dvorinka/Devour.git
synced 2026-06-03 20:13:03 +00:00
updage
This commit is contained in:
@@ -0,0 +1,612 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Enhanced Devour Scorecard Generator - Extended version with more visual elements.
|
||||
Enhanced data visualization with additional metrics and improved layout.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Tuple, Any, Optional
|
||||
|
||||
try:
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
except ImportError:
|
||||
print("Error: PIL/Pillow required. Install with: pip install Pillow")
|
||||
sys.exit(1)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Enhanced visual constants
|
||||
SCALE = 2
|
||||
BG = (248, 248, 246)
|
||||
FRAME = (222, 222, 220)
|
||||
BORDER = (200, 200, 198)
|
||||
ACCENT = (88, 166, 255)
|
||||
TEXT = (40, 44, 52)
|
||||
DIM = (140, 140, 140)
|
||||
BG_SCORE = (255, 255, 255)
|
||||
BG_TABLE = (255, 255, 255)
|
||||
BG_ROW_ALT = (250, 250, 248)
|
||||
BG_GRADIENT_START = (240, 240, 238)
|
||||
BG_GRADIENT_END = (248, 248, 246)
|
||||
|
||||
# Extended color palette
|
||||
COLORS = {
|
||||
'excellent': (68, 120, 68), # deep sage
|
||||
'good': (120, 140, 72), # olive green
|
||||
'moderate': (145, 155, 80), # yellow-green
|
||||
'poor': (255, 193, 7), # orange
|
||||
'critical': (220, 38, 127), # red
|
||||
'info': (88, 166, 255), # blue accent
|
||||
'warning': (255, 152, 0), # orange accent
|
||||
}
|
||||
|
||||
@dataclass
|
||||
class EnhancedScorecardData:
|
||||
"""Enhanced data structure for comprehensive scorecard."""
|
||||
project_name: str
|
||||
version: str
|
||||
main_score: float
|
||||
strict_score: float
|
||||
dimensions: List[Tuple[str, Dict[str, Any]]]
|
||||
trends: Dict[str, List[float]]
|
||||
recommendations: List[str]
|
||||
metadata: Dict[str, Any]
|
||||
|
||||
|
||||
def draw_mini_sparkline(
|
||||
draw: ImageDraw.ImageDraw,
|
||||
x: int, y: int, width: int, height: int,
|
||||
values: List[float], color: Tuple[int, int, int]
|
||||
) -> None:
|
||||
"""Draw a mini sparkline chart."""
|
||||
if len(values) < 2:
|
||||
return
|
||||
|
||||
# Draw background
|
||||
draw.rectangle([x, y, x + width, y + height], fill=BG_TABLE, outline=BORDER)
|
||||
|
||||
# Calculate points
|
||||
padding = 3
|
||||
chart_width = width - 2 * padding
|
||||
chart_height = height - 2 * padding
|
||||
step_x = chart_width / (len(values) - 1)
|
||||
|
||||
points = []
|
||||
for i, value in enumerate(values):
|
||||
px = x + padding + i * step_x
|
||||
py = y + padding + chart_height - (value / 100) * chart_height
|
||||
points.append((px, py))
|
||||
|
||||
# Draw line
|
||||
if len(points) > 1:
|
||||
draw.line(points, fill=color, width=2)
|
||||
|
||||
# Draw points
|
||||
for px, py in points:
|
||||
draw.ellipse([px - 2, py - 2, px + 2, py + 2], fill=color, outline=TEXT)
|
||||
|
||||
|
||||
def draw_progress_ring(
|
||||
draw: ImageDraw.ImageDraw,
|
||||
cx: int, cy: int, outer_radius: int,
|
||||
progress: float, color: Tuple[int, int, int],
|
||||
label: str = ""
|
||||
) -> None:
|
||||
"""Draw a circular progress ring."""
|
||||
# Background ring
|
||||
draw.ellipse([cx - outer_radius, cy - outer_radius, cx + outer_radius, cy + outer_radius],
|
||||
fill=BG_TABLE, outline=BORDER, width=2)
|
||||
|
||||
# Progress arc
|
||||
if progress > 0:
|
||||
inner_radius = outer_radius - 8
|
||||
start_angle = 270
|
||||
end_angle = start_angle - (progress / 100) * 360
|
||||
|
||||
# Draw multiple arcs for thickness effect
|
||||
for i in range(3):
|
||||
radius_offset = i * 2
|
||||
draw.arc([cx - outer_radius + radius_offset, cy - outer_radius + radius_offset,
|
||||
cx + outer_radius + radius_offset, cy + outer_radius + radius_offset],
|
||||
start=start_angle, end=end_angle,
|
||||
fill=color, width=6 - i * 2)
|
||||
|
||||
# Center text
|
||||
if label:
|
||||
font = load_font(10, bold=True)
|
||||
bbox = draw.textbbox((0, 0), label, font=font)
|
||||
text_width = bbox[2] - bbox[0]
|
||||
text_height = bbox[3] - bbox[1]
|
||||
|
||||
draw.text((cx - text_width // 2, cy - text_height // 2 + bbox[1]),
|
||||
label, fill=TEXT, font=font)
|
||||
|
||||
|
||||
def draw_trend_indicator(
|
||||
draw: ImageDraw.ImageDraw,
|
||||
x: int, y: int, size: int,
|
||||
trend: str, value: float
|
||||
) -> None:
|
||||
"""Draw a trend indicator with arrow."""
|
||||
# Background circle
|
||||
draw.ellipse([x, y, x + size, y + size], fill=BG_TABLE, outline=BORDER)
|
||||
|
||||
# Trend arrow
|
||||
cx, cy = x + size // 2, y + size // 2
|
||||
arrow_size = size // 4
|
||||
|
||||
color = COLORS['good'] if trend == 'up' else COLORS['poor'] if trend == 'down' else COLORS['moderate']
|
||||
|
||||
if trend == 'up':
|
||||
# Up arrow
|
||||
points = [
|
||||
(cx, cy + arrow_size),
|
||||
(cx - arrow_size // 2, cy),
|
||||
(cx + arrow_size // 2, cy)
|
||||
]
|
||||
elif trend == 'down':
|
||||
# Down arrow
|
||||
points = [
|
||||
(cx, cy - arrow_size),
|
||||
(cx - arrow_size // 2, cy),
|
||||
(cx + arrow_size // 2, cy)
|
||||
]
|
||||
else:
|
||||
# Circle for stable
|
||||
draw.ellipse([cx - arrow_size // 2, cy - arrow_size // 2,
|
||||
cx + arrow_size // 2, cy + arrow_size // 2],
|
||||
fill=color, outline=TEXT)
|
||||
return
|
||||
|
||||
draw.polygon(points, fill=color)
|
||||
|
||||
# Value text
|
||||
font = load_font(8)
|
||||
text = f"{value:.0f}%"
|
||||
bbox = draw.textbbox((0, 0), text, font=font)
|
||||
text_width = bbox[2] - bbox[0]
|
||||
|
||||
draw.text((cx - text_width // 2, y + size + 5), text, fill=TEXT, font=font)
|
||||
|
||||
|
||||
def generate_enhanced_scorecard(data: EnhancedScorecardData, output_path: str | Path) -> Path:
|
||||
"""Generate enhanced scorecard with additional visual elements."""
|
||||
output_path = Path(output_path)
|
||||
|
||||
# Layout - larger canvas for more elements
|
||||
width = scale(900)
|
||||
height = scale(700)
|
||||
margin = scale(20)
|
||||
|
||||
# Create image with gradient background
|
||||
img = Image.new("RGB", (width, height), BG)
|
||||
draw = ImageDraw.Draw(img)
|
||||
|
||||
# Add subtle gradient overlay
|
||||
for i in range(height):
|
||||
alpha = i / height
|
||||
color = tuple(
|
||||
int(BG_GRADIENT_START[j] + alpha * (BG_GRADIENT_END[j] - BG_GRADIENT_START[j]))
|
||||
for j in range(3)
|
||||
)
|
||||
draw.line([(0, i), (width, i)], fill=color)
|
||||
|
||||
# Header section with enhanced styling
|
||||
header_height = scale(100)
|
||||
draw_header_section(draw, margin, data, header_height)
|
||||
|
||||
# Main content area
|
||||
content_y = margin + header_height + scale(20)
|
||||
|
||||
# Left panel - enhanced score display
|
||||
left_panel_width = scale(280)
|
||||
draw_enhanced_left_panel(draw, margin, content_y, left_panel_width, data)
|
||||
|
||||
# Right panel - dimensions with trends
|
||||
right_panel_x = margin + left_panel_width + scale(20)
|
||||
right_panel_width = width - right_panel_x - margin
|
||||
draw_enhanced_right_panel(draw, right_panel_x, content_y, right_panel_width, data)
|
||||
|
||||
# Bottom recommendations
|
||||
recommendations_y = content_y + scale(200)
|
||||
draw_recommendations_section(draw, margin, recommendations_y, width - 2 * margin, data.recommendations)
|
||||
|
||||
# Footer with metadata
|
||||
footer_y = height - scale(40)
|
||||
draw_footer_section(draw, margin, footer_y, width - 2 * margin, data.metadata)
|
||||
|
||||
# Save image
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
img.save(str(output_path), "PNG", optimize=True)
|
||||
return output_path
|
||||
|
||||
|
||||
def draw_header_section(
|
||||
draw: ImageDraw.ImageDraw,
|
||||
x: int, y: int, width: int,
|
||||
data: EnhancedScorecardData
|
||||
) -> None:
|
||||
"""Draw enhanced header section."""
|
||||
# Project title
|
||||
font_title = load_font(18, bold=True)
|
||||
title = f"{data.project_name} Code Health"
|
||||
title_bbox = draw.textbbox((0, 0), title, font=font_title)
|
||||
title_width = title_bbox[2] - title_bbox[0]
|
||||
|
||||
# Background panel for title
|
||||
panel_height = scale(40)
|
||||
draw.rectangle([x, y, x + width, y + panel_height],
|
||||
fill=BG_SCORE, outline=ACCENT, width=2)
|
||||
|
||||
draw.text((x + (width - title_width) // 2, y + scale(12)),
|
||||
title, fill=TEXT, font=font_title)
|
||||
|
||||
# Version and timestamp
|
||||
font_info = load_font(10)
|
||||
info_text = f"v{data.version} - Generated {data.metadata.get('timestamp', '')}"
|
||||
draw.text((x, y + panel_height + scale(5), info_text, fill=DIM, font=font_info)
|
||||
|
||||
|
||||
def draw_enhanced_left_panel(
|
||||
draw: ImageDraw.ImageDraw,
|
||||
x: int, y: int, width: int,
|
||||
data: EnhancedScorecardData
|
||||
) -> None:
|
||||
"""Draw enhanced left panel with multiple visual elements."""
|
||||
# Main score with large display
|
||||
score_y = y + scale(20)
|
||||
score_size = scale(80)
|
||||
|
||||
# Circular progress indicator
|
||||
draw_progress_ring(draw, x + width // 2 - score_size // 2, score_y, score_size // 2,
|
||||
progress=data.main_score, color=score_to_color(data.main_score))
|
||||
|
||||
# Score text
|
||||
font_score = load_font(36, bold=True)
|
||||
score_text = f"{int(data.main_score)}"
|
||||
score_bbox = draw.textbbox((0, 0), score_text, font=font_score)
|
||||
score_width = score_bbox[2] - score_bbox[0]
|
||||
score_height = score_bbox[3] - score_bbox[1]
|
||||
|
||||
score_bg_y = score_y + score_size + scale(10)
|
||||
score_bg_height = scale(50)
|
||||
|
||||
# Background for score text
|
||||
draw.rectangle([x, score_bg_y, x + width, score_bg_y + score_bg_height],
|
||||
fill=BG_SCORE, outline=BORDER, width=1)
|
||||
|
||||
draw.text((x + (width - score_width) // 2, score_bg_y + score_bg_height // 2 - score_height // 2 + score_bbox[1]),
|
||||
score_text, fill=TEXT, font=font_score)
|
||||
|
||||
# Strict score
|
||||
strict_y = score_bg_y + score_bg_height + scale(15)
|
||||
font_strict = load_font(14)
|
||||
strict_text = f"Strict: {int(data.strict_score)}"
|
||||
strict_bbox = draw.textbbox((0, 0), strict_text, font=font_strict)
|
||||
strict_width = strict_bbox[2] - strict_bbox[0]
|
||||
|
||||
draw.text((x + (width - strict_width) // 2, strict_y - strict_bbox[1]),
|
||||
strict_text, fill=score_to_color(data.strict_score, muted=True), font=font_strict)
|
||||
|
||||
# Grade indicator
|
||||
grade_y = strict_y + scale(30)
|
||||
grade_size = scale(40)
|
||||
grade = "A" if data.main_score >= 90 else "B" if data.main_score >= 70 else "C" if data.main_score >= 50 else "D" if data.main_score >= 30 else "F"
|
||||
|
||||
draw_progress_ring(draw, x + width // 2 - grade_size // 2, grade_y, grade_size // 2,
|
||||
progress=data.main_score, color=score_to_color(data.main_score))
|
||||
|
||||
font_grade = load_font(24, bold=True)
|
||||
grade_bbox = draw.textbbox((0, 0), grade, font=font_grade)
|
||||
grade_width = grade_bbox[2] - grade_bbox[0]
|
||||
|
||||
draw.text((x + (width - grade_width) // 2, grade_y + grade_size + scale(10) - grade_bbox[1]),
|
||||
grade, fill=TEXT, font=font_grade)
|
||||
|
||||
|
||||
def draw_enhanced_right_panel(
|
||||
draw: ImageDraw.ImageDraw,
|
||||
x: int, y: int, width: int,
|
||||
data: EnhancedScorecardData
|
||||
) -> None:
|
||||
"""Draw enhanced right panel with dimensions and trends."""
|
||||
# Dimensions table
|
||||
table_y = y + scale(20)
|
||||
table_height = scale(120)
|
||||
|
||||
draw_enhanced_dimensions_table(draw, x, table_y, width, table_height, data.dimensions)
|
||||
|
||||
# Trends section
|
||||
trends_y = table_y + table_height + scale(20)
|
||||
draw_trends_section(draw, x, trends_y, width, data.trends)
|
||||
|
||||
|
||||
def draw_enhanced_dimensions_table(
|
||||
draw: ImageDraw.ImageDraw,
|
||||
x: int, y: int, width: int, height: int,
|
||||
dimensions: List[Tuple[str, Dict[str, Any]]]
|
||||
) -> None:
|
||||
"""Draw enhanced dimensions table with sparklines."""
|
||||
font_header = load_font(11, bold=True)
|
||||
font_row = load_font(9)
|
||||
|
||||
# Table header
|
||||
header_y = y
|
||||
draw.text((x, header_y), "CATEGORY", fill=TEXT, font=font_header)
|
||||
draw.text((x + scale(120), header_y), "SCORE", fill=TEXT, font=font_header)
|
||||
draw.text((x + scale(200), header_y), "TREND", fill=TEXT, font=font_header)
|
||||
|
||||
# Table rows
|
||||
row_y = header_y + scale(20)
|
||||
row_height = scale(25)
|
||||
|
||||
for i, (name, data) in enumerate(dimensions[:4]): # Limit to 4 rows
|
||||
# Background
|
||||
if i % 2 == 1:
|
||||
draw.rectangle([x, row_y, x + width, row_y + row_height], fill=BG_ROW_ALT)
|
||||
|
||||
# Category name
|
||||
draw.text((x + scale(5), row_y + scale(5), name, fill=TEXT, font=font_row)
|
||||
|
||||
# Score bar
|
||||
score = data.get('score', 0)
|
||||
bar_x = x + scale(120)
|
||||
bar_width = scale(60)
|
||||
bar_height = scale(10)
|
||||
draw_metric_bar(draw, bar_x, row_y + scale(5), bar_width, bar_height,
|
||||
score, 100, "", score_to_color(score))
|
||||
|
||||
# Trend indicator
|
||||
trend_x = x + scale(200)
|
||||
trend_data = data.get('trend', [50, 55, 60]) # Sample trend data
|
||||
if len(trend_data) >= 2:
|
||||
trend = 'up' if trend_data[-1] > trend_data[-2] else 'down' if trend_data[-1] < trend_data[-2] else 'stable'
|
||||
draw_trend_indicator(draw, trend_x, row_y + scale(5), scale(20), trend, trend_data[-1])
|
||||
|
||||
row_y += row_height
|
||||
|
||||
|
||||
def draw_trends_section(
|
||||
draw: ImageDraw.ImageDraw,
|
||||
x: int, y: int, width: int,
|
||||
trends: Dict[str, List[float]]
|
||||
) -> None:
|
||||
"""Draw trends section with sparklines."""
|
||||
font_header = load_font(11, bold=True)
|
||||
|
||||
# Section title
|
||||
draw.text((x, y), "Recent Trends", fill=TEXT, font=font_header)
|
||||
|
||||
# Trend charts
|
||||
chart_y = y + scale(30)
|
||||
chart_width = scale(80)
|
||||
chart_height = scale(40)
|
||||
|
||||
for i, (category, values) in enumerate(list(trends.items())[:2]): # 2 charts
|
||||
chart_x = x + i * (chart_width + scale(20))
|
||||
|
||||
# Category label
|
||||
font_label = load_font(9)
|
||||
draw.text((chart_x, chart_y - scale(15), category, fill=TEXT, font=font_label)
|
||||
|
||||
# Sparkline
|
||||
draw_mini_sparkline(draw, chart_x, chart_y, chart_width, chart_height,
|
||||
values, COLORS['info'])
|
||||
|
||||
|
||||
def draw_recommendations_section(
|
||||
draw: ImageDraw.ImageDraw,
|
||||
x: int, y: int, width: int,
|
||||
recommendations: List[str]
|
||||
) -> None:
|
||||
"""Draw recommendations section."""
|
||||
font_header = load_font(12, bold=True)
|
||||
font_rec = load_font(9)
|
||||
|
||||
# Section title
|
||||
draw.text((x, y), "🎯 Priority Actions", fill=TEXT, font=font_header)
|
||||
|
||||
# Recommendations list
|
||||
rec_y = y + scale(25)
|
||||
for i, rec in enumerate(recommendations[:4]): # Limit to 4 recommendations
|
||||
# Numbered bullet
|
||||
bullet = f"{i + 1}."
|
||||
draw.text((x + scale(10), rec_y, bullet, fill=ACCENT, font=font_rec)
|
||||
|
||||
# Recommendation text (with word wrap)
|
||||
text_x = x + scale(30)
|
||||
max_width = width - scale(40)
|
||||
|
||||
# Simple word wrap
|
||||
words = rec.split()
|
||||
line = ""
|
||||
for word in words:
|
||||
test_line = line + " " + word if line else word
|
||||
bbox = draw.textbbox((0, 0), test_line, font=font_rec)
|
||||
text_width = bbox[2] - bbox[0]
|
||||
|
||||
if text_width > max_width or line:
|
||||
draw.text((text_x, rec_y), line, fill=TEXT, font=font_rec)
|
||||
rec_y += scale(12)
|
||||
line = word if line else ""
|
||||
text_x = x + scale(30)
|
||||
else:
|
||||
line = test_line
|
||||
|
||||
rec_y += scale(15)
|
||||
|
||||
|
||||
def draw_footer_section(
|
||||
draw: ImageDraw.ImageDraw,
|
||||
x: int, y: int, width: int,
|
||||
metadata: Dict[str, Any]
|
||||
) -> None:
|
||||
"""Draw footer with metadata."""
|
||||
font_footer = load_font(8)
|
||||
|
||||
# Footer line
|
||||
draw.line([(x, y), (x + width, y)], fill=BORDER, width=1)
|
||||
|
||||
# Metadata text
|
||||
info_text = f"Generated by Devour v{metadata.get('version', '1.0.0')} • {metadata.get('files_analyzed', 0)} files analyzed • {metadata.get('timestamp', '')}"
|
||||
draw.text((x, y + scale(5), info_text, fill=DIM, font=font_footer)
|
||||
|
||||
|
||||
def draw_metric_bar(
|
||||
draw: ImageDraw.ImageDraw,
|
||||
x: int, y: int, width: int, height: int,
|
||||
value: float, max_value: float,
|
||||
label: str, color: Tuple[int, int, int]
|
||||
) -> None:
|
||||
"""Draw a horizontal metric bar."""
|
||||
# Background
|
||||
draw.rectangle([x, y, x + width, y + height], fill=BG_TABLE, outline=BORDER)
|
||||
|
||||
# Fill bar
|
||||
if max_value > 0:
|
||||
fill_width = int((value / max_value) * width)
|
||||
draw.rectangle([x, y, x + fill_width, y + height], fill=color)
|
||||
|
||||
|
||||
def score_to_color(score: float) -> Tuple[int, int, int]:
|
||||
"""Convert score to color."""
|
||||
if score >= 90:
|
||||
return COLORS['excellent']
|
||||
elif score >= 70:
|
||||
return COLORS['good']
|
||||
elif score >= 50:
|
||||
return COLORS['moderate']
|
||||
elif score >= 30:
|
||||
return COLORS['poor']
|
||||
else:
|
||||
return COLORS['critical']
|
||||
|
||||
|
||||
def load_font(size: int, *, bold: bool = False) -> ImageFont.ImageFont:
|
||||
"""Load font with fallback."""
|
||||
size = size * SCALE
|
||||
candidates = [
|
||||
"/System/Library/Fonts/SFCompact.ttf",
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
||||
"arial.ttf"
|
||||
]
|
||||
|
||||
if bold:
|
||||
candidates = [
|
||||
"/System/Library/Fonts/SFCompact.ttf",
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
|
||||
"arialbd.ttf"
|
||||
]
|
||||
|
||||
for path in candidates:
|
||||
try:
|
||||
if os.path.exists(path):
|
||||
return ImageFont.truetype(path, size)
|
||||
except OSError:
|
||||
continue
|
||||
|
||||
return ImageFont.load_default()
|
||||
|
||||
|
||||
def load_enhanced_devour_data(json_path: str) -> EnhancedScorecardData:
|
||||
"""Load Devour data and convert to enhanced format."""
|
||||
with open(json_path, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
findings = data.get('findings', [])
|
||||
|
||||
# Calculate scores
|
||||
total_score = sum(f.get('score', 0) * int(f.get('severity', 1)) for f in findings)
|
||||
main_score = max(0, 100 - (total_score / 1000 * 100))
|
||||
strict_score = main_score * 0.8 # Strict is lower
|
||||
|
||||
# Group by type
|
||||
type_counts = {}
|
||||
type_scores = {}
|
||||
|
||||
for finding in findings:
|
||||
ftype = finding.get('type', 'unknown')
|
||||
type_counts[ftype] = type_counts.get(ftype, 0) + 1
|
||||
type_scores[ftype] = type_scores.get(ftype, 0) + finding.get('score', 0)
|
||||
|
||||
# Create dimensions with trend data
|
||||
dimensions = []
|
||||
for ftype, count in type_counts.items():
|
||||
avg_score = 100 - (type_scores[ftype] / max(1, count) / 10 * 100)
|
||||
|
||||
# Generate sample trend data
|
||||
trend_data = [avg_score - 10, avg_score - 5, avg_score, avg_score + 5, avg_score + 10]
|
||||
|
||||
dimensions.append((
|
||||
ftype.replace('_', ' ').title(),
|
||||
{
|
||||
'score': max(0, min(100, avg_score)),
|
||||
'strict': max(0, min(100, avg_score * 0.8)),
|
||||
'count': count,
|
||||
'trend': trend_data
|
||||
}
|
||||
))
|
||||
|
||||
# Sort by score (lowest first)
|
||||
dimensions.sort(key=lambda x: x[1]['score'])
|
||||
|
||||
# Generate recommendations
|
||||
recommendations = [
|
||||
"🔴 Address critical T4 issues immediately for maximum impact",
|
||||
"🟡 Focus on reducing high-severity findings (T2-T3)",
|
||||
"🟢 Improve test coverage to reduce technical debt",
|
||||
"📊 Regular code reviews to maintain quality standards"
|
||||
]
|
||||
|
||||
# Metadata
|
||||
metadata = {
|
||||
'version': '1.0.0',
|
||||
'files_analyzed': len(set(f.get('file', '') for f in findings)),
|
||||
'timestamp': data.get('timestamp', '')
|
||||
}
|
||||
|
||||
return EnhancedScorecardData(
|
||||
project_name="Devour",
|
||||
version="1.0.0",
|
||||
main_score=main_score,
|
||||
strict_score=strict_score,
|
||||
dimensions=dimensions[:6], # Limit to 6 dimensions
|
||||
trends={'overall': [main_score - 5, main_score], 'performance': [85, 88, 92, main_score]},
|
||||
recommendations=recommendations,
|
||||
metadata=metadata
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
if len(sys.argv) != 3:
|
||||
print("Usage: python devour_enhanced.py <devour_results.json> <output.png>")
|
||||
sys.exit(1)
|
||||
|
||||
json_path = sys.argv[1]
|
||||
output_path = sys.argv[2]
|
||||
|
||||
if not os.path.exists(json_path):
|
||||
print(f"Error: Input file {json_path} not found")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
data = load_enhanced_devour_data(json_path)
|
||||
result_path = generate_enhanced_scorecard(data, output_path)
|
||||
print(f"Enhanced scorecard generated: {result_path}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error generating enhanced scorecard: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user