Skip to content

fix(metrics): make TurboMQ the sum of cluster factors (not the mean)#26

Merged
lemduc merged 1 commit into
mainfrom
fix/turbomq-sum
Jul 24, 2026
Merged

fix(metrics): make TurboMQ the sum of cluster factors (not the mean)#26
lemduc merged 1 commit into
mainfrom
fix/turbomq-sum

Conversation

@lemduc

@lemduc lemduc commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Closes #25

Root cause

compute_turbo_mq documented TurboMQ as the sum of per-component cluster factors but returned turbo_mq / num_components. Since BasicMQ's per-component term intra/(intra + 0.5·inter) is algebraically the same cluster factor as TurboMQ's 2·intra/(2·intra + inter), the mean made the two metrics identical for every input — one signal reported as two.

Formula change

  • TurboMQ now returns Σ CF(i) (Mitchell & Mancoridis / Bunch), so its range is [0, k] for k components. details gains normalized (sum / k) and num_components; raw_sum is kept for backward compatibility.
  • BasicMQ is unchanged — it remains the normalized (mean) variant, TurboMQ / k, in [0, 1].
  • compute_balanced_scores now reads TurboMQ's normalized value for cohesion_family, so every derived score stays bounded to [0, 1] and its numeric value is unchanged.

Docs touched in-repo: compute_metrics tool docstring, README "Balanced Architecture Score" section, and .github/skills/improve-architecture-metrics/SKILL.md (TurboMQ is no longer comparable across differing component counts — use BasicMQ for that).

Tests

Regression test added first (confirmed red on the old code, green after):

  • test_turbo_mq_is_the_sum_of_cluster_factors — value equals the sum of details["cluster_factors"]; fixture arithmetic pinned (Calc CF = 0.5, Util CF = 0.0 → 0.5, where the mean would be 0.25).
  • test_turbo_mq_and_basic_mq_are_not_identical — on the k = 2 fixture, TurboMQ != BasicMQ and TurboMQ == k * BasicMQ.
  • test_turbo_mq bound relaxed from [0, 1] to [0, k].

pytest: 231 passed, 13 skipped. The 4 failures in tests/test_github_action_distribution.py are pre-existing on main and unrelated — they assert the workflow/README pin the current package version, which was not propagated in the v0.2.0 release commit (8090d12).

ruff check src/ tests/: clean. mypy src/arcade_agent/algorithms/coupling.py: the only 2 errors are the pre-existing _score_drivers dict-item errors, unchanged by this PR.

Changed baseline / report values

.arcade/baseline.json stores architecture only (components/rationale), no metric values — it is unchanged by this PR, and the arch-drift workflow regenerates it on push to main anyway.

Self-analysis of this repo (arcade-agent.ci.run_self_analysis --algorithm pkg --filter-non-architectural-helpers, k = 9 components), before → after:

Metric Before After
TurboMQ 0.2493 2.2438
BasicMQ 0.2493 0.2493 (unchanged)
RCI 0.5652 0.5652 (unchanged)
IntraConnectivity 0.0182 0.0182 (unchanged)
InterConnectivity 0.0323 0.0323 (unchanged)
TwoWayPairRatio 0.0 0.0 (unchanged)
BalancedArchitectureScore 0.6193 0.6193 (unchanged)
PrincipleAlignmentScore 0.8553 0.8553 (unchanged)

TurboMQ is the only value that moves; it moves by exactly ×k. The first PR self-analysis comment after this merges will show a large one-off TurboMQ jump against the stored baseline artifact — that is the rescale, not an architectural change.

The committed sample reports examples/arcade_core_report.html and examples/comparison_report.html embed a pre-fix TurboMQ (0.291) from an external project run. They are static illustrations, are not asserted by any test, and cannot be regenerated here (source project not available), so they are left as-is.

Follow-up (do not do in this PR)

The live docs site arcade-agent.dev (repo lemduc/arcade-agent-site, page docs/reference/metrics) currently documents the TurboMQ ≡ BasicMQ equivalence as intended behavior — accurate before this change, wrong after. It must be updated after this PR merges. That repo is deliberately untouched here.

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

Architecture Drift Report

Algorithm: PKG | Entities: 673 | Components: 7

Drift from Baseline

Metric Baseline Current Delta
Components 7 7 +0
Similarity 1.00
BalancedArchitectureScore 0.68
PrincipleAlignmentScore 0.84
RCI 0.99
TurboMQ 1.66
BasicMQ 0.24
IntraConnectivity 0.00
InterConnectivity 0.01
TwoWayPairRatio 0.00
DependencyHealth 0.99
ComponentBalance 0.23
HubBalance 0.67
BoundaryClarity 1.00
DependencyDistribution 0.57
SmellDiscipline 0.91

Changes

  • 3 entity movement(s) between components

Smells (1)

  • Concern Overload: Tests

Generated by arcade-agent

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

🤖 Architecture Analysis Summary

Powered by arcade-agent — automatic architectural self-analysis


📈 Metric Evolution

Baseline commit: 746fc56

Legend: 🟢 better · 🔴 worse · 🟡 low impact · ⚪ no change

Metric Baseline Current Change
BalancedArchitectureScore 0.6193 0.6193 → (no change)
📦 Components 9 9 → (no change)
🧩 Entities 115 115 → (no change)
🔗 Edges 115 115 → (no change)
🏷️ Classes 22 22 → (no change)
ƒ Functions 271 272 🟡 ↑ (+1.0000)
🔧 Methods 34 34 → (no change)
RCI 0.5652 0.5652 → (no change)
TurboMQ 0.2493 2.2438 🟢 ↑ (+1.9945)
BasicMQ 0.2493 0.2493 → (no change)
IntraConnectivity 0.0182 0.0182 → (no change)
InterConnectivity 0.0323 0.0323 → (no change)
TwoWayPairRatio 0.0000 0.0000 → (no change)
DependencyHealth 0.9822 0.9822 → (no change)
ComponentBalance 0.5304 0.5304 → (no change)
HubBalance 0.5000 0.5000 → (no change)
BoundaryClarity 0.9444 0.9444 → (no change)
DependencyDistribution 0.6263 0.6263 → (no change)
SmellDiscipline 0.9333 0.9333 → (no change)
PrincipleAlignmentScore 0.8553 0.8553 → (no change)

🏛️ Current Architecture

Metric Value
📦 Components 9
🧩 Entities 115
🔗 Edges 115
🏷️ Classes 22
ƒ Functions 272
🔧 Methods 34
Balanced Score 🟡 0.6193 (Fair)
Principle Alignment 0.8553
RCI 0.5652
TurboMQ 2.2438
BasicMQ 0.2493
IntraConnectivity 0.0182
InterConnectivity 0.0323
TwoWayPairRatio 0.0000
DependencyHealth 0.9822
ComponentBalance 0.5304
HubBalance 0.5000
BoundaryClarity 0.9444
DependencyDistribution 0.6263
SmellDiscipline 0.9333

🧭 Principle Signals

Signal Value
AcyclicDependencies 1.0000
LayeringHealth 0.9867
ResponsibilityFocus 0.9333
InterfaceSegregation 1.0000
ComponentBalance 0.5304
HubBalance 0.5000
BoundaryClarity 0.9444
DependencyDistribution 0.6263
SmellDiscipline 0.9333

🎯 Score Drivers

Biggest risks

  • HubBalance: gap=0.5000 (signal=0.5000)
  • ComponentBalance: gap=0.4696 (signal=0.5304)
  • DependencyDistribution: gap=0.3737 (signal=0.6263)

Strongest areas

  • AcyclicDependencies: gap=0.0000 (signal=1.0000)
  • InterfaceSegregation: gap=0.0000 (signal=1.0000)
  • LayeringHealth: gap=0.0133 (signal=0.9867)

🕸️ High-Level Design

graph TD
    Algorithms["Algorithms\n39 entities\n5 classes / 3 methods"]
    Budget["Budget\n3 entities\n0 classes / 0 methods"]
    Cache["Cache\n4 entities\n0 classes / 0 methods"]
    Ci["Ci\n5 entities\n0 classes / 0 methods"]
    Exporters["Exporters\n14 entities\n1 classes / 0 methods"]
    Incremental["Incremental\n2 entities\n1 classes / 2 methods"]
    Parsers["Parsers\n17 entities\n11 classes / 27 methods"]
    Serialization["Serialization\n8 entities\n0 classes / 0 methods"]
    Tools["Tools\n23 entities\n4 classes / 2 methods"]
    Cache --> Serialization
    Ci --> Algorithms
    Ci --> Exporters
    Ci --> Serialization
    Ci --> Tools
    Serialization --> Algorithms
    Serialization --> Parsers
    Tools --> Algorithms
    Tools --> Cache
    Tools --> Exporters
    Tools --> Parsers
Loading
🏗️ Components breakdown
Component Entities Classes Methods
Algorithms 39 5 3
Tools 23 4 2
Parsers 17 11 27
Exporters 14 1 0
Serialization 8 0 0
Ci 5 0 0
Cache 4 0 0
Budget 3 0 0
Incremental 2 1 2

🚨 Architectural Smells

Severity Type Affected Components
🟡 medium Concern Overload Tools

📈 Evolution vs Baseline

Baseline commit: 746fc56

Architecture-to-Architecture (A2A) Comparison

Metric Value
A2A Similarity 1.0000
Matched Components 9
Components Added 0
Components Removed 0
Component matching details

Matched:

Baseline Current Similarity
Algorithms Algorithms 1.0000
Budget Budget 1.0000
Cache Cache 1.0000
Ci Ci 1.0000
Exporters Exporters 1.0000
Incremental Incremental 1.0000
Parsers Parsers 1.0000
Serialization Serialization 1.0000
Tools Tools 1.0000
High-level component statistics
Status Baseline Current Similarity Entities Classes Methods
matched Algorithms Algorithms 1.0000 39 → 39 (0) 5 → 5 (0) 3 → 3 (0)
matched Budget Budget 1.0000 3 → 3 (0) 0 → 0 (0) 0 → 0 (0)
matched Cache Cache 1.0000 4 → 4 (0) 0 → 0 (0) 0 → 0 (0)
matched Ci Ci 1.0000 5 → 5 (0) 0 → 0 (0) 0 → 0 (0)
matched Exporters Exporters 1.0000 14 → 14 (0) 1 → 1 (0) 0 → 0 (0)
matched Incremental Incremental 1.0000 2 → 2 (0) 1 → 1 (0) 2 → 2 (0)
matched Parsers Parsers 1.0000 17 → 17 (0) 11 → 11 (0) 27 → 27 (0)
matched Serialization Serialization 1.0000 8 → 8 (0) 0 → 0 (0) 0 → 0 (0)
matched Tools Tools 1.0000 23 → 23 (0) 4 → 4 (0) 2 → 2 (0)
Before/After Mermaid diagrams

Baseline

graph TD
    Algorithms["Algorithms\n39 entities\n5 classes / 3 methods"]
    Budget["Budget\n3 entities\n0 classes / 0 methods"]
    Cache["Cache\n4 entities\n0 classes / 0 methods"]
    Ci["Ci\n5 entities\n0 classes / 0 methods"]
    Exporters["Exporters\n14 entities\n1 classes / 0 methods"]
    Incremental["Incremental\n2 entities\n1 classes / 2 methods"]
    Parsers["Parsers\n17 entities\n11 classes / 27 methods"]
    Serialization["Serialization\n8 entities\n0 classes / 0 methods"]
    Tools["Tools\n23 entities\n4 classes / 2 methods"]
    Cache --> Serialization
    Ci --> Algorithms
    Ci --> Exporters
    Ci --> Serialization
    Ci --> Tools
    Serialization --> Algorithms
    Serialization --> Parsers
    Tools --> Algorithms
    Tools --> Cache
    Tools --> Exporters
    Tools --> Parsers
Loading

Current

graph TD
    Algorithms["Algorithms\n39 entities\n5 classes / 3 methods"]
    Budget["Budget\n3 entities\n0 classes / 0 methods"]
    Cache["Cache\n4 entities\n0 classes / 0 methods"]
    Ci["Ci\n5 entities\n0 classes / 0 methods"]
    Exporters["Exporters\n14 entities\n1 classes / 0 methods"]
    Incremental["Incremental\n2 entities\n1 classes / 2 methods"]
    Parsers["Parsers\n17 entities\n11 classes / 27 methods"]
    Serialization["Serialization\n8 entities\n0 classes / 0 methods"]
    Tools["Tools\n23 entities\n4 classes / 2 methods"]
    Cache --> Serialization
    Ci --> Algorithms
    Ci --> Exporters
    Ci --> Serialization
    Ci --> Tools
    Serialization --> Algorithms
    Serialization --> Parsers
    Tools --> Algorithms
    Tools --> Cache
    Tools --> Exporters
    Tools --> Parsers
Loading
Component dependency delta
Status Source Target
matched No dependency delta -

💡 CI/CD Insights

  • Quality Score: 🟡 Fair (BalancedArchitectureScore=0.6193)
  • Principle Alignment: 0.8553 (higher means cleaner layering, focus, and boundaries)
  • Top Risk Driver: HubBalance (signal=0.5000)
  • Trend: ➡️ Stable architectural quality
  • Architecture Stability: 🟢 High (A2A=1.0000)
  • Smells: ⚠️ 1 smell(s) — review suggested

📄 View HTML reports and artifacts


This comment is auto-generated by the self-dogfooding CI job. It updates on every push to this PR.

@lemduc

lemduc commented Jul 22, 2026

Copy link
Copy Markdown
Owner Author

Split out the unrelated commit: the chore(ci): pin distributed action/workflow examples to v0.2.0 fix now lives in its own PR #28, and this branch has been force-pushed to drop it (now a single commit, 3211219).

Consequence for this PR's CI: it will go red again until #28 merges, with 4 failures in tests/test_github_action_distribution.py. Those are the pre-existing main breakage, not the TurboMQ change — merge #28 first, then this rebases green.

TurboMQ's docstring described the Bunch-style sum of per-component cluster
factors, but the body returned the mean (sum / k), which is algebraically
identical to BasicMQ. The two metrics therefore always reported the same
number, presenting one signal as two.

Return the raw sum per Mitchell & Mancoridis, keep BasicMQ as the normalized
mean, and expose `normalized` / `num_components` in TurboMQ's details. Derived
balanced scores consume the normalized form so they stay bounded to [0, 1] and
their values are unchanged.

Closes #25

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@lemduc
lemduc force-pushed the fix/turbomq-sum branch from 3211219 to 9099194 Compare July 22, 2026 08:42
@lemduc
lemduc merged commit fc32023 into main Jul 24, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TurboMQ and BasicMQ are algebraically identical as implemented; TurboMQ docstring says sum but body returns mean

1 participant