Skip to content

Commit 29a4b9f

Browse files
Dynamic ECharts dashboard: replace PNGs with interactive charts
- Add chart_data.py to export all report data as JSON for ECharts - Add interactive_report.py with tabbed dashboard (Basic, Team, Risk, Fairness, Advanced) - Fix chart resize on tab switch (charts in hidden panels had zero dimensions) - Add bar/boxplot width config for few-category charts - Filter empty weeks from stacked bar charts for sparse teams - Add Playwright verify_dashboard.py and test_dashboard_charts.py - Rename core reports to basic, update master_report and runner - Add .playwright-mcp/ to gitignore Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent e3f0cf9 commit 29a4b9f

11 files changed

Lines changed: 983 additions & 17 deletions

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,11 @@ cache/
5858

5959
# Reports output (generated charts, migration logs) - keep reports/*.py tracked
6060
reports/*.log
61-
reports/core/*.png
61+
reports/basic/*.png
6262
reports/team/**/*.png
6363
reports/risk/*.png
6464
reports/fairness/*.png
6565
reports/advanced/*.png
66+
reports/index.html
6667

68+
.playwright-mcp/

reports/advanced/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
"""Advanced reports (15, 16)."""
1+
"""Advanced reports (15, 16, 21)."""
22

33
from .reports import (
44
report_complexity_trend_by_team,
55
report_cumulative_complexity,
6+
report_developer_line_velocity,
67
)

reports/advanced/reports.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,43 @@ def _ensure_date(df: pd.DataFrame) -> pd.DataFrame:
1919
return df
2020

2121

22+
def report_developer_line_velocity(df: pd.DataFrame, output_dir: Path) -> Optional[str]:
23+
"""Report: Developer Velocity by Week - one line per developer, x-axis = weeks."""
24+
df = _ensure_date(df)
25+
if df.empty:
26+
return None
27+
df = df.copy()
28+
dev_col = "developer" if "developer" in df.columns else "author"
29+
df["developer"] = df.get(dev_col, pd.Series([""] * len(df))).fillna("").astype(str)
30+
df = df[df["developer"] != ""]
31+
if df.empty:
32+
return None
33+
df["week"] = pd.to_datetime(df["date"]).dt.to_period("W").dt.start_time
34+
weekly = df.groupby(["week", "developer"])["complexity"].sum().unstack(fill_value=0)
35+
if weekly.empty or weekly.shape[1] == 0:
36+
return None
37+
# Sort developers by total complexity (desc) for legend order
38+
weekly = weekly.reindex(weekly.sum().sort_values(ascending=False).index, axis=1)
39+
fig, ax = plt.subplots(figsize=(14, 7))
40+
for col in weekly.columns:
41+
ax.plot(weekly.index, weekly[col], label=col, alpha=0.8)
42+
ax.set_title(
43+
"Developer Velocity by Week\n"
44+
"What: Complexity delivered per developer per week. When: Track individual output. How: Each line = one developer."
45+
)
46+
ax.set_ylabel("Complexity")
47+
ax.set_xlabel("Week")
48+
ax.legend(bbox_to_anchor=(1.02, 1), loc="upper left", ncol=2, fontsize=8)
49+
ax.tick_params(axis="x", rotation=45)
50+
ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m-%d"))
51+
ax.set_ylim(bottom=0)
52+
fig.tight_layout()
53+
out = output_dir / "21-developer-line-velocity.png"
54+
fig.savefig(out, dpi=150, bbox_inches="tight")
55+
plt.close(fig)
56+
return str(out) if validate_png_has_content(out) else None
57+
58+
2259
def report_complexity_trend_by_team(df: pd.DataFrame, output_dir: Path) -> Optional[str]:
2360
"""Report 15: Complexity Trend by Team - rolling median."""
2461
df = _ensure_date(df)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Core operational reports (1-3, 7)."""
1+
"""Basic operational reports (1-3, 7, 18, 19)."""
22

33
from .reports import (
44
report_avg_complexity_rolling,

0 commit comments

Comments
 (0)