feat(analyzer): influence from eigenvector centrality over the real review graph

Change

Replace the projectID/100 "cross-group" proxy (30% of the influence score) with a named network centrality computed over the real review/collaboration graph manifold already builds in connections.go.

GitLab project IDs are global creation-order integers with no namespace relationship, so bucketing by projectID/100 measured noise, not organizational reach. The methodology (round 1 section 4.1; round 2 section 4) calls for a centrality over the real edges. The projectID/100 bucketing is removed entirely.

Centrality method + weighting (per the methodology doc, section 2.4)

influence = 0.5*eigenvector_centrality + 0.3*review_reach + 0.2*project_breadth
  • Eigenvector centrality (50%, dominant): Bonacich 1987. Power iteration on the symmetric weighted adjacency built from the same edges ComputeConnections builds: Merge Request (MR) reviewer/author pairs, MR merge-user/author pairs, and project co-membership (with the same large-project member cap). L2-normalized each step, capped at 100 iterations, tolerance 1e-9. Pure standard library.
  • Review reach (30%): distinct MR authors reviewed; a transparent degree baseline (Freeman 1978).
  • Project breadth (20%): distinct projects active in.

Each factor is percentile-normalized to [0,1], then weighted-summed. Edge-definition caveat noted in code (Nia 2010): centrality is sensitive to how the graph is built; the edge set is reported (undirected weighted review + merge + co-membership).

New constants (Centrality, CentralityMaxIter, CentralityTolerance) live in the Methodology config. Dashboard influence-methodology prose updated to match.

Before / after (captured 300-user baseline)

Top-10 influencers, before (projectID/100 blend) vs after (centrality blend):

Rank BEFORE user (score) AFTER user (score)
1 300 (0.981) 203 (0.881)
2 298 (0.979) 98 (0.855)
3 292 (0.974) 111 (0.818)
4 275 (0.963) 121 (0.806)
5 279 (0.952) 85 (0.798)
6 299 (0.950) 105 (0.796)
7 290 (0.948) 210 (0.792)
8 285 (0.946) 79 (0.788)
9 291 (0.942) 205 (0.785)
10 288 (0.941) 204 (0.784)

Top-10 overlap: 0 of 10. The old top-10 was dominated by high-numbered (late-created) user IDs touching high-ID projects, the exact projectID/100 artifact. The new top-10 surfaces users who are genuinely central in the review graph.

Distribution (n=301, all nonzero in both):

min median max nonzero
BEFORE 0.026 0.295 0.981 301/301
AFTER 0.001 0.382 0.881 301/301

Downstream effects

Influence-only, as expected. Domain scores (0 changed), archetypes (0 changed), gaps (identical), health score (43 -> 43), archetype distribution, domain statistics, and adoption sentiment are all unchanged.

The one legitimate downstream consumer is team risk_score: ComputeTeamRisk blends 0.3*(1 - avg_influence), so all 20 teams shifted risk_score slightly (no other team field changed). This is reported honestly, not a regression.

Validation

  • go vet ./...: clean
  • CGO_ENABLED=1 go test -race ./...: pass (all packages)
  • golangci-lint: 0 issues
  • coverage: 67.5% total (>= 65% gate); analyzer package 94.0%
  • Deterministic: a double-analyze of the same input is byte-identical across profiles, archetypes, teams, gaps, summary.

Test values: existing influence tests assert ordering/bounds invariants (not pinned values) and still pass under the new formula. Added TestInfluenceScores_EigenvectorBlend pinning the new computed outputs with a comment. No test unrelated to influence changed.

Edited by Andrew Dunn

Merge request reports

Loading