diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts
index 5a23952b..464cd9d7 100644
--- a/frontend/src/lib/utils.ts
+++ b/frontend/src/lib/utils.ts
@@ -27,6 +27,10 @@ export function formatCost(dollars: number): string {
return `$${dollars.toFixed(2)}`
}
+export function titleize(value: string): string {
+ return value.replaceAll("_", " ")
+}
+
export function formatLabel(value: string): string {
return value
.replaceAll("_", " ")
diff --git a/frontend/src/pages/__tests__/growth.test.tsx b/frontend/src/pages/__tests__/growth.test.tsx
index 71ce7740..ffd1e2ac 100644
--- a/frontend/src/pages/__tests__/growth.test.tsx
+++ b/frontend/src/pages/__tests__/growth.test.tsx
@@ -34,6 +34,9 @@ vi.mock("@/components/growth/onboarding-recommendations", () => ({
vi.mock("@/components/growth/pattern-summary", () => ({
PatternSummary: () =>
pattern summary
,
}))
+vi.mock("@/components/growth/bright-spot-cards", () => ({
+ BrightSpotCards: () =>
bright spots
,
+}))
vi.mock("@/components/growth/shared-pattern-card", () => ({
SharedPatternCards: () =>
shared patterns
,
}))
diff --git a/frontend/src/pages/growth.tsx b/frontend/src/pages/growth.tsx
index 51109635..0a957c48 100644
--- a/frontend/src/pages/growth.tsx
+++ b/frontend/src/pages/growth.tsx
@@ -18,6 +18,7 @@ import { NewHireTable } from "@/components/growth/new-hire-table"
import { VelocityChart } from "@/components/growth/velocity-chart"
import { OnboardingRecommendations } from "@/components/growth/onboarding-recommendations"
import { PatternSummary } from "@/components/growth/pattern-summary"
+import { BrightSpotCards } from "@/components/growth/bright-spot-cards"
import { SharedPatternCards } from "@/components/growth/shared-pattern-card"
import { SkillInventorySummary } from "@/components/insights/skill-inventory-summary"
import { CoverageSummary } from "@/components/growth/coverage-summary"
@@ -67,6 +68,7 @@ function PatternsTab({ teamId, startDate, endDate }: TabProps) {
return (
)
diff --git a/frontend/src/types/api.ts b/frontend/src/types/api.ts
index e94e825b..1182be14 100644
--- a/frontend/src/types/api.ts
+++ b/frontend/src/types/api.ts
@@ -811,8 +811,26 @@ export interface SharedPattern {
insight: string
}
+export interface BrightSpot {
+ bright_spot_id: string
+ title: string
+ summary: string
+ cluster_type: string
+ cluster_label: string
+ session_count: number
+ engineer_count: number
+ success_rate: number | null
+ avg_duration: number | null
+ exemplar_session_id: string
+ exemplar_engineer_id: string
+ exemplar_engineer_name: string
+ exemplar_duration_seconds: number | null
+ exemplar_tools: string[]
+}
+
export interface PatternSharingResponse {
patterns: SharedPattern[]
+ bright_spots: BrightSpot[]
total_clusters_found: number
sessions_analyzed: number
}
diff --git a/src/primer/common/schemas.py b/src/primer/common/schemas.py
index a66965e1..782c2299 100644
--- a/src/primer/common/schemas.py
+++ b/src/primer/common/schemas.py
@@ -1149,8 +1149,26 @@ class SharedPattern(BaseModel):
insight: str
+class BrightSpot(BaseModel):
+ bright_spot_id: str
+ title: str
+ summary: str
+ cluster_type: str
+ cluster_label: str
+ session_count: int
+ engineer_count: int
+ success_rate: float | None
+ avg_duration: float | None
+ exemplar_session_id: str
+ exemplar_engineer_id: str
+ exemplar_engineer_name: str
+ exemplar_duration_seconds: float | None
+ exemplar_tools: list[str]
+
+
class PatternSharingResponse(BaseModel):
patterns: list[SharedPattern]
+ bright_spots: list[BrightSpot] = []
total_clusters_found: int
sessions_analyzed: int
diff --git a/src/primer/server/services/insights_service.py b/src/primer/server/services/insights_service.py
index b5489f1c..05afc69d 100644
--- a/src/primer/server/services/insights_service.py
+++ b/src/primer/server/services/insights_service.py
@@ -11,6 +11,7 @@
from primer.common.models import Engineer, SessionFacets, ToolUsage
from primer.common.models import Session as SessionModel
from primer.common.schemas import (
+ BrightSpot,
CohortMetrics,
ConfigOptimizationResponse,
ConfigSuggestion,
@@ -1025,11 +1026,61 @@ def _build_pattern(
return PatternSharingResponse(
patterns=patterns,
+ bright_spots=_derive_bright_spots(patterns),
total_clusters_found=len(patterns),
sessions_analyzed=len(sessions),
)
+def _derive_bright_spots(patterns: list[SharedPattern], limit: int = 3) -> list[BrightSpot]:
+ candidates = [
+ pattern
+ for pattern in patterns
+ if pattern.best_approach is not None
+ and pattern.success_rate is not None
+ and pattern.success_rate >= 0.75
+ and pattern.engineer_count >= 2
+ and pattern.session_count >= 3
+ ]
+ candidates.sort(
+ key=lambda pattern: (
+ -(pattern.success_rate or 0.0),
+ -pattern.engineer_count,
+ -pattern.session_count,
+ pattern.avg_duration or float("inf"),
+ )
+ )
+
+ bright_spots: list[BrightSpot] = []
+ for pattern in candidates[:limit]:
+ exemplar = pattern.best_approach
+ if exemplar is None:
+ continue
+ bright_spots.append(
+ BrightSpot(
+ bright_spot_id=pattern.cluster_id,
+ title=f"Bright spot: {pattern.cluster_label}",
+ summary=(
+ f"{pattern.engineer_count} engineers converged on this pattern across "
+ f"{pattern.session_count} sessions. {exemplar.name}'s exemplar session is the "
+ "best place to copy the approach."
+ ),
+ cluster_type=pattern.cluster_type,
+ cluster_label=pattern.cluster_label,
+ session_count=pattern.session_count,
+ engineer_count=pattern.engineer_count,
+ success_rate=pattern.success_rate,
+ avg_duration=pattern.avg_duration,
+ exemplar_session_id=exemplar.session_id,
+ exemplar_engineer_id=exemplar.engineer_id,
+ exemplar_engineer_name=exemplar.name,
+ exemplar_duration_seconds=exemplar.duration_seconds,
+ exemplar_tools=exemplar.tools_used,
+ )
+ )
+ return bright_spots
+
+
# ---------------------------------------------------------------------------
# Onboarding Acceleration
# ---------------------------------------------------------------------------
diff --git a/tests/test_growth.py b/tests/test_growth.py
index cc2be4bb..54147f53 100644
--- a/tests/test_growth.py
+++ b/tests/test_growth.py
@@ -279,6 +279,47 @@ def test_best_approach_selects_fastest_successful(self, client, db_session, admi
assert best is not None
assert best["engineer_id"] == engineers[1].id
+ def test_bright_spots_surface_exemplar_sessions_for_strong_patterns(
+ self, client, db_session, admin_headers
+ ):
+ team, engineers = _create_team_engineers(db_session, 3)
+ now = datetime.now(UTC)
+
+ durations = [180.0, 60.0, 120.0]
+ session_ids = []
+ for engineer, duration in zip(engineers, durations, strict=True):
+ session = _create_session(
+ db_session,
+ engineer,
+ started_at=now - timedelta(hours=1),
+ project_name="primer",
+ duration_seconds=duration,
+ )
+ session_ids.append(session.id)
+ db_session.add(
+ SessionFacets(session_id=session.id, session_type="debugging", outcome="success")
+ )
+ db_session.add(ToolUsage(session_id=session.id, tool_name="Read", call_count=5))
+ db_session.add(ToolUsage(session_id=session.id, tool_name="Edit", call_count=2))
+ db_session.flush()
+
+ response = client.get(
+ f"/api/v1/analytics/pattern-sharing?team_id={team.id}",
+ headers=admin_headers,
+ )
+ assert response.status_code == 200
+ data = response.json()
+
+ assert len(data["bright_spots"]) >= 1
+ bright_spot = data["bright_spots"][0]
+ assert bright_spot["cluster_label"] == "debugging on primer"
+ assert bright_spot["session_count"] == 3
+ assert bright_spot["engineer_count"] == 3
+ assert bright_spot["success_rate"] == 1.0
+ assert bright_spot["exemplar_engineer_id"] == engineers[1].id
+ assert bright_spot["exemplar_session_id"] == session_ids[1]
+ assert bright_spot["exemplar_tools"] == ["Edit", "Read"]
+
def test_best_approach_treats_legacy_success_as_success(
self, client, db_session, admin_headers
):