|
| 1 | +#!/usr/bin/env python3 |
| 2 | +""" |
| 3 | +Generate a diagram explaining the core principles of the complexity analyzer engine. |
| 4 | +""" |
| 5 | + |
| 6 | +import matplotlib.pyplot as plt |
| 7 | +import matplotlib.patches as mpatches |
| 8 | +from matplotlib.patches import FancyBboxPatch, FancyArrowPatch, Rectangle |
| 9 | +import matplotlib.lines as mlines |
| 10 | + |
| 11 | +# Colors - modern palette |
| 12 | +COLORS = { |
| 13 | + "input": "#E3F2FD", # Light blue |
| 14 | + "process": "#FFF3E0", # Light orange |
| 15 | + "llm": "#E8F5E9", # Light green |
| 16 | + "output": "#F3E5F5", # Light purple |
| 17 | + "accent": "#1976D2", # Blue |
| 18 | + "text": "#212121", |
| 19 | + "border": "#BDBDBD", |
| 20 | +} |
| 21 | + |
| 22 | + |
| 23 | +def add_box(ax, x, y, w, h, text, color, fontsize=9): |
| 24 | + """Add a rounded box with text.""" |
| 25 | + box = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.02", |
| 26 | + facecolor=color, edgecolor=COLORS["border"], linewidth=1) |
| 27 | + ax.add_patch(box) |
| 28 | + ax.text(x + w/2, y + h/2, text, ha="center", va="center", fontsize=fontsize, |
| 29 | + wrap=True, color=COLORS["text"]) |
| 30 | + |
| 31 | + |
| 32 | +def add_arrow(ax, start, end, color="#757575"): |
| 33 | + """Add an arrow between two points.""" |
| 34 | + ax.annotate("", xy=end, xytext=start, |
| 35 | + arrowprops=dict(arrowstyle="->", color=color, lw=2)) |
| 36 | + |
| 37 | + |
| 38 | +def main(): |
| 39 | + fig, ax = plt.subplots(1, 1, figsize=(14, 12)) |
| 40 | + ax.set_xlim(0, 14) |
| 41 | + ax.set_ylim(0, 12) |
| 42 | + ax.set_aspect("equal") |
| 43 | + ax.axis("off") |
| 44 | + |
| 45 | + # Title |
| 46 | + ax.text(7, 11.5, "Complexity Analyzer — Core Engine", fontsize=18, ha="center", |
| 47 | + fontweight="bold", color=COLORS["text"]) |
| 48 | + ax.text(7, 11, "Implementation effort (1–10), not line count or operational risk", |
| 49 | + fontsize=10, ha="center", color="#616161") |
| 50 | + |
| 51 | + # === PIPELINE FLOW === |
| 52 | + # Row 1: Input |
| 53 | + add_box(ax, 0.5, 9.2, 2.2, 1.2, "PR URL", COLORS["input"]) |
| 54 | + add_box(ax, 3.2, 9.2, 2.2, 1.2, "GitHub API\nFetch", COLORS["process"]) |
| 55 | + add_box(ax, 5.9, 9.2, 2.2, 1.2, "Raw Diff\n+ Metadata", COLORS["input"]) |
| 56 | + |
| 57 | + add_arrow(ax, (2.7, 9.8), (3.2, 9.8)) |
| 58 | + add_arrow(ax, (5.4, 9.8), (5.9, 9.8)) |
| 59 | + |
| 60 | + # Row 2: Preprocess |
| 61 | + add_box(ax, 5.9, 7.2, 2.5, 1.5, "Preprocess\n(redact, filter,\nchunk, truncate)", COLORS["process"]) |
| 62 | + add_arrow(ax, (6.05, 9.2), (6.05, 8.7)) |
| 63 | + |
| 64 | + add_box(ax, 5.9, 5.4, 2.5, 1.5, "Build Prompt\n(URL, title, stats,\ndiff excerpt)", COLORS["process"]) |
| 65 | + add_arrow(ax, (7.15, 7.2), (7.15, 6.9)) |
| 66 | + |
| 67 | + # Row 3: LLM |
| 68 | + add_box(ax, 2.5, 3.2, 4.5, 2.0, "LLM Analysis\n(OpenAI / Anthropic / Bedrock)\n\nSystem prompt + diff → JSON", COLORS["llm"], fontsize=10) |
| 69 | + add_arrow(ax, (7.15, 6.1), (7, 5.2)) |
| 70 | + add_arrow(ax, (7, 5.2), (5.25, 5.2)) |
| 71 | + add_arrow(ax, (5.25, 5.2), (5.25, 5.2)) |
| 72 | + |
| 73 | + # Row 4: Output |
| 74 | + add_box(ax, 2.5, 1.2, 2.0, 1.5, "Parse & Validate\n(clamp 1–10)", COLORS["output"]) |
| 75 | + add_box(ax, 5.0, 1.2, 2.0, 1.5, "Score + Explanation", COLORS["output"]) |
| 76 | + add_arrow(ax, (4.25, 4.2), (3.5, 2.7)) |
| 77 | + add_arrow(ax, (3.5, 1.95), (5.0, 1.95)) |
| 78 | + |
| 79 | + # === PRINCIPLES PANEL === |
| 80 | + principles_y = 0.3 |
| 81 | + ax.add_patch(Rectangle((8.5, 1.5), 5.2, 8.5, facecolor="#FAFAFA", |
| 82 | + edgecolor=COLORS["border"], linewidth=1, linestyle="-")) |
| 83 | + ax.text(11.1, 9.7, "Core Principles", fontsize=12, ha="center", fontweight="bold") |
| 84 | + |
| 85 | + principles = [ |
| 86 | + ("What Complexity Means", [ |
| 87 | + "Implementation effort", |
| 88 | + "Design + code + testing", |
| 89 | + "Developer velocity proxy", |
| 90 | + ]), |
| 91 | + ("LLM Factors", [ |
| 92 | + "Scope (files/modules)", |
| 93 | + "Logic (control flow, abstractions)", |
| 94 | + "Testing effort implied", |
| 95 | + "Data (migrations, mappings)", |
| 96 | + ]), |
| 97 | + ("Score Bands", [ |
| 98 | + "1–2: Almost trivial", |
| 99 | + "3–4: Small but non-trivial", |
| 100 | + "5–6: Medium (multi-module)", |
| 101 | + "7–8: Large/sophisticated", |
| 102 | + "9–10: Very complex", |
| 103 | + ]), |
| 104 | + ("Avoid Conflating", [ |
| 105 | + "Line count ≠ complexity", |
| 106 | + "Lockfile churn ≠ high score", |
| 107 | + "Operational risk ≠ impl. difficulty", |
| 108 | + ]), |
| 109 | + ] |
| 110 | + |
| 111 | + y = 9.2 |
| 112 | + for title, items in principles: |
| 113 | + ax.text(8.7, y, title, fontsize=9, fontweight="bold", color=COLORS["accent"]) |
| 114 | + y -= 0.35 |
| 115 | + for item in items: |
| 116 | + ax.text(8.9, y, f"• {item}", fontsize=8, color=COLORS["text"]) |
| 117 | + y -= 0.3 |
| 118 | + y -= 0.2 |
| 119 | + |
| 120 | + # === PREPROCESS DETAILS === |
| 121 | + ax.add_patch(Rectangle((0.5, 1.5), 4.5, 3.2, facecolor="#FFFDE7", |
| 122 | + edgecolor=COLORS["border"], linewidth=1)) |
| 123 | + ax.text(2.75, 4.5, "Preprocess Steps", fontsize=10, ha="center", fontweight="bold") |
| 124 | + preprocess_steps = [ |
| 125 | + "Redact: secrets, emails, keys", |
| 126 | + "Filter: lockfiles, binary, vendor/", |
| 127 | + "Chunk: max hunks per file (default 2)", |
| 128 | + "Truncate: tiktoken, max 50k tokens", |
| 129 | + "Stats: additions, deletions, byExt, byLang", |
| 130 | + ] |
| 131 | + for i, step in enumerate(preprocess_steps): |
| 132 | + ax.text(0.7, 4.2 - i * 0.5, step, fontsize=8) |
| 133 | + |
| 134 | + # File layout legend |
| 135 | + ax.text(7, 0.5, "Key files: cli/analyze.py (orchestration) • cli/preprocess.py • cli/prompt/default.txt • cli/scoring.py • cli/llm*.py", |
| 136 | + fontsize=8, ha="center", style="italic", color="#757575") |
| 137 | + |
| 138 | + plt.tight_layout() |
| 139 | + out_path = "reports/complexity-engine-principles.png" |
| 140 | + fig.savefig(out_path, dpi=150, bbox_inches="tight", facecolor="white") |
| 141 | + plt.close(fig) |
| 142 | + print(f"Saved: {out_path}") |
| 143 | + |
| 144 | + |
| 145 | +if __name__ == "__main__": |
| 146 | + main() |
0 commit comments