From d9f333b63bd22c8d27b21b580dec27a1452b72db Mon Sep 17 00:00:00 2001 From: "Charles C. Figueiredo" Date: Mon, 16 Mar 2026 17:01:59 -0400 Subject: [PATCH] feat: add exemplar session library --- ROADMAP.md | 12 +- .../exemplar-session-library.test.tsx | 67 ++++++ .../growth/exemplar-session-library.tsx | 152 +++++++++++++ frontend/src/pages/__tests__/growth.test.tsx | 26 +++ frontend/src/pages/growth.tsx | 10 + frontend/src/types/api.ts | 35 +++ src/primer/common/schemas.py | 35 +++ .../server/services/insights_service.py | 202 +++++++++++++++++- tests/test_growth.py | 87 +++++++- 9 files changed, 618 insertions(+), 8 deletions(-) create mode 100644 frontend/src/components/growth/__tests__/exemplar-session-library.test.tsx create mode 100644 frontend/src/components/growth/exemplar-session-library.tsx diff --git a/ROADMAP.md b/ROADMAP.md index 58db654..e0d5caa 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -102,7 +102,7 @@ As usage grows, the platform needs stronger derived data pipelines, performance - [ ] [P1] Cursor-specific workflow fingerprinting and session archetype mapping - [x] [P1] Session archetype detection: debugging, feature delivery, refactor, migration, docs, investigation - [ ] [P1] Delegation graph capture for multi-agent and subagent workflows -- [ ] [P2] Exemplar session library for high-value workflows and onboarding examples +- [x] [P2] Exemplar session library for high-value workflows and onboarding examples - [ ] [P2] Prompt, skill, and template reuse analytics by workflow and outcome ## Friction & Bottleneck Analysis @@ -144,7 +144,7 @@ As usage grows, the platform needs stronger derived data pipelines, performance - [x] Onboarding velocity scoring - [x] Onboarding recommendations - [x] Shared behavior pattern discovery with approach comparison -- [ ] [P1] Bright spot detection: explicitly surface high performers and cross-pollinate their patterns +- [x] [P1] Bright spot detection: explicitly surface high performers and cross-pollinate their patterns - [ ] [P1] Exemplar-session-to-learning-path pipeline - [ ] [P1] Team skill gap mapping by workflow, tool category, and project context - [ ] [P2] Coaching program measurement: which onboarding or training changes improved outcomes @@ -206,7 +206,8 @@ As usage grows, the platform needs stronger derived data pipelines, performance - [x] Budget tracking with burn-rate alerts and projected overrun warnings - [x] Cost per successful outcome metric - [ ] [P1] Break-even analysis for API vs seat-based pricing with per-engineer recommendations -- [ ] [P1] Cost per workflow archetype and cost per engineering outcome +- [x] [P1] Cost per workflow archetype and cost per engineering outcome +- [x] [P1] Workflow compare mode for archetype and fingerprint performance - [ ] [P1] Model-choice opportunity scoring for overspend reduction - [ ] [P2] Budget policy simulation by team, project, and billing model @@ -222,6 +223,11 @@ As usage grows, the platform needs stronger derived data pipelines, performance - [ ] [P2] Weekly manager review packs that combine quality, friction, growth, and cost - [ ] [P2] Recommendation narratives that explain why a workflow is likely to help +## Website & Positioning + +- [ ] [P1] Reposition the website around workflow intelligence for agentic engineering +- [ ] [P1] Showcase workflow cost, quality, compare mode, and exemplar sessions as the core proof points + ## Interventions & Experimentation - [x] [P0] Recommendation-to-intervention workflow with owner, status, due date, and linked evidence diff --git a/frontend/src/components/growth/__tests__/exemplar-session-library.test.tsx b/frontend/src/components/growth/__tests__/exemplar-session-library.test.tsx new file mode 100644 index 0000000..695fb7a --- /dev/null +++ b/frontend/src/components/growth/__tests__/exemplar-session-library.test.tsx @@ -0,0 +1,67 @@ +import { render, screen } from "@testing-library/react" +import { MemoryRouter } from "react-router-dom" + +import { ExemplarSessionLibrary } from "@/components/growth/exemplar-session-library" + +describe("ExemplarSessionLibrary", () => { + it("renders an empty state", () => { + render( + + + , + ) + + expect(screen.getByText("No exemplar sessions identified yet.")).toBeInTheDocument() + }) + + it("renders workflow-aware exemplar cards", () => { + render( + + edit -> execute -> fix", + summary: "Backed by 3 engineers across 4 sessions.", + why_selected: "Chosen as the fastest successful example for debugging on primer.", + session_id: "sess-1", + engineer_id: "eng-1", + engineer_name: "Alice Example", + project_name: "primer", + outcome: "success", + helpfulness: "very_helpful", + session_summary: "Resolved the failing auth flow after reproducing locally.", + duration_seconds: 180, + estimated_cost: 1.24, + tools_used: ["Read", "Edit", "Bash"], + workflow_archetype: "debugging", + workflow_fingerprint: "debugging: read -> edit -> execute -> fix", + workflow_steps: ["read", "edit", "execute", "fix"], + supporting_session_count: 4, + supporting_engineer_count: 3, + supporting_pattern_count: 2, + success_rate: 1, + linked_patterns: [ + { + cluster_id: "cluster-1", + cluster_type: "session_type", + cluster_label: "debugging on primer", + session_count: 4, + engineer_count: 3, + success_rate: 1, + }, + ], + }, + ]} + /> + , + ) + + expect(screen.getAllByText("debugging: read -> edit -> execute -> fix")).toHaveLength(2) + expect(screen.getByText("Why This Exemplar")).toBeInTheDocument() + expect(screen.getByText("Workflow Evidence")).toBeInTheDocument() + expect(screen.getByText("$1.24")).toBeInTheDocument() + expect(screen.getByText("debugging on primer")).toBeInTheDocument() + expect(screen.getByText("Open exemplar")).toHaveAttribute("href", "/sessions/sess-1") + }) +}) diff --git a/frontend/src/components/growth/exemplar-session-library.tsx b/frontend/src/components/growth/exemplar-session-library.tsx new file mode 100644 index 0000000..f9cd3ce --- /dev/null +++ b/frontend/src/components/growth/exemplar-session-library.tsx @@ -0,0 +1,152 @@ +import { Link } from "react-router-dom" + +import { Badge } from "@/components/ui/badge" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { formatCost, formatDuration, formatLabel, formatPercent, titleize } from "@/lib/utils" +import type { ExemplarSession } from "@/types/api" + +interface ExemplarSessionLibraryProps { + exemplars: ExemplarSession[] +} + +export function ExemplarSessionLibrary({ exemplars }: ExemplarSessionLibraryProps) { + if (exemplars.length === 0) { + return ( +
+ No exemplar sessions identified yet. +
+ ) + } + + return ( +
+ {exemplars.map((exemplar) => ( + + +
+
+ {exemplar.title} +

{exemplar.summary}

+
+
+ {exemplar.workflow_archetype && ( + {formatLabel(exemplar.workflow_archetype)} + )} + {exemplar.project_name && ( + {exemplar.project_name} + )} +
+
+
+ +
+
+

Support

+

{exemplar.supporting_engineer_count}

+

+ {exemplar.supporting_session_count} sessions +

+
+
+

Success

+

{formatPercent(exemplar.success_rate)}

+
+
+

Duration

+

{formatDuration(exemplar.duration_seconds)}

+
+
+

Cost

+

+ {exemplar.estimated_cost != null ? formatCost(exemplar.estimated_cost) : "-"} +

+
+
+ +
+

+ Why This Exemplar +

+

{exemplar.why_selected}

+
+ + {exemplar.session_summary && ( +
+

+ Session Summary +

+

{exemplar.session_summary}

+
+ )} + +
+
+

+ Workflow Evidence +

+
+ {exemplar.workflow_fingerprint ? ( + {exemplar.workflow_fingerprint} + ) : ( + No workflow fingerprint yet + )} + {exemplar.workflow_steps.map((step) => ( + + {titleize(step)} + + ))} +
+
+ +
+

+ Exemplar Tools +

+
+ {exemplar.tools_used.length === 0 ? ( + No tool telemetry + ) : ( + exemplar.tools_used.map((tool) => ( + + {tool} + + )) + )} +
+
+
+ +
+

+ Linked Patterns +

+
+ {exemplar.linked_patterns.map((pattern) => ( + + {pattern.cluster_label} + + ))} +
+
+ +
+
+

{exemplar.engineer_name}

+

+ {exemplar.outcome ? formatLabel(exemplar.outcome) : "Outcome unknown"} + {exemplar.helpfulness ? ` • ${formatLabel(exemplar.helpfulness)}` : ""} +

+
+ + Open exemplar + +
+
+
+ ))} +
+ ) +} diff --git a/frontend/src/pages/__tests__/growth.test.tsx b/frontend/src/pages/__tests__/growth.test.tsx index ffd1e2a..474b0c4 100644 --- a/frontend/src/pages/__tests__/growth.test.tsx +++ b/frontend/src/pages/__tests__/growth.test.tsx @@ -37,6 +37,9 @@ vi.mock("@/components/growth/pattern-summary", () => ({ vi.mock("@/components/growth/bright-spot-cards", () => ({ BrightSpotCards: () =>
bright spots
, })) +vi.mock("@/components/growth/exemplar-session-library", () => ({ + ExemplarSessionLibrary: () =>
exemplar session library
, +})) vi.mock("@/components/growth/shared-pattern-card", () => ({ SharedPatternCards: () =>
shared patterns
, })) @@ -166,6 +169,29 @@ describe("GrowthPage", () => { ).toBeInTheDocument() }) + it("shows exemplar library content on the patterns tab", () => { + mockUsePatternSharing.mockReturnValue({ + data: { + patterns: [], + bright_spots: [], + exemplar_sessions: [], + total_clusters_found: 0, + sessions_analyzed: 0, + }, + isLoading: false, + } as unknown as ReturnType) + mockUseEngineerProfile.mockReturnValue({ + data: null, + isLoading: false, + } as unknown as ReturnType) + + renderPage() + fireEvent.click(screen.getByRole("button", { name: "Patterns" })) + + expect(screen.getByText("Exemplar Session Library")).toBeInTheDocument() + expect(screen.getByText("exemplar session library")).toBeInTheDocument() + }) + it("shows an engineer chooser for API-key admins without a selected context", () => { mockGetApiKey.mockReturnValue("test-key") mockUseAuth.mockReturnValue({ user: null } as ReturnType) diff --git a/frontend/src/pages/growth.tsx b/frontend/src/pages/growth.tsx index 0a957c4..f083a43 100644 --- a/frontend/src/pages/growth.tsx +++ b/frontend/src/pages/growth.tsx @@ -19,6 +19,7 @@ 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 { ExemplarSessionLibrary } from "@/components/growth/exemplar-session-library" import { SharedPatternCards } from "@/components/growth/shared-pattern-card" import { SkillInventorySummary } from "@/components/insights/skill-inventory-summary" import { CoverageSummary } from "@/components/growth/coverage-summary" @@ -69,6 +70,15 @@ function PatternsTab({ teamId, startDate, endDate }: TabProps) {
+
+
+

Exemplar Session Library

+

+ Browse concrete sessions worth copying, with workflow, support, and cost context. +

+
+ +
) diff --git a/frontend/src/types/api.ts b/frontend/src/types/api.ts index 1182be1..5df5042 100644 --- a/frontend/src/types/api.ts +++ b/frontend/src/types/api.ts @@ -828,9 +828,44 @@ export interface BrightSpot { exemplar_tools: string[] } +export interface ExemplarPatternReference { + cluster_id: string + cluster_type: string + cluster_label: string + session_count: number + engineer_count: number + success_rate: number | null +} + +export interface ExemplarSession { + exemplar_id: string + title: string + summary: string + why_selected: string + session_id: string + engineer_id: string + engineer_name: string + project_name: string | null + outcome: string | null + helpfulness: string | null + session_summary: string | null + duration_seconds: number | null + estimated_cost: number | null + tools_used: string[] + workflow_archetype: string | null + workflow_fingerprint: string | null + workflow_steps: string[] + supporting_session_count: number + supporting_engineer_count: number + supporting_pattern_count: number + success_rate: number | null + linked_patterns: ExemplarPatternReference[] +} + export interface PatternSharingResponse { patterns: SharedPattern[] bright_spots: BrightSpot[] + exemplar_sessions: ExemplarSession[] total_clusters_found: number sessions_analyzed: number } diff --git a/src/primer/common/schemas.py b/src/primer/common/schemas.py index 782c229..5fc816e 100644 --- a/src/primer/common/schemas.py +++ b/src/primer/common/schemas.py @@ -1166,9 +1166,44 @@ class BrightSpot(BaseModel): exemplar_tools: list[str] +class ExemplarPatternReference(BaseModel): + cluster_id: str + cluster_type: str + cluster_label: str + session_count: int + engineer_count: int + success_rate: float | None + + +class ExemplarSession(BaseModel): + exemplar_id: str + title: str + summary: str + why_selected: str + session_id: str + engineer_id: str + engineer_name: str + project_name: str | None = None + outcome: str | None = None + helpfulness: str | None = None + session_summary: str | None = None + duration_seconds: float | None + estimated_cost: float | None = None + tools_used: list[str] + workflow_archetype: str | None = None + workflow_fingerprint: str | None = None + workflow_steps: list[str] = [] + supporting_session_count: int + supporting_engineer_count: int + supporting_pattern_count: int + success_rate: float | None + linked_patterns: list[ExemplarPatternReference] + + class PatternSharingResponse(BaseModel): patterns: list[SharedPattern] bright_spots: list[BrightSpot] = [] + exemplar_sessions: list[ExemplarSession] = [] 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 05afc69..bfbb365 100644 --- a/src/primer/server/services/insights_service.py +++ b/src/primer/server/services/insights_service.py @@ -8,8 +8,15 @@ from sqlalchemy.orm import Session from primer.common.facet_taxonomy import canonical_outcome, is_success_outcome -from primer.common.models import Engineer, SessionFacets, ToolUsage +from primer.common.models import ( + Engineer, + ModelUsage, + SessionFacets, + SessionWorkflowProfile, + ToolUsage, +) from primer.common.models import Session as SessionModel +from primer.common.pricing import estimate_cost from primer.common.schemas import ( BrightSpot, CohortMetrics, @@ -19,6 +26,8 @@ EngineerLearningPath, EngineerRampup, EngineerSkillProfile, + ExemplarPatternReference, + ExemplarSession, LearningPathsResponse, LearningRecommendation, NewHireProgress, @@ -35,9 +44,7 @@ TimeToTeamAverageResponse, WeeklySuccessPoint, ) -from primer.server.services.analytics_service import ( - base_session_query, -) +from primer.server.services.analytics_service import base_session_query def _shannon_entropy(counts: dict[str, int]) -> float: @@ -883,6 +890,7 @@ def get_pattern_sharing( SessionFacets.session_type, SessionFacets.outcome, SessionFacets.agent_helpfulness, + SessionFacets.brief_summary, SessionFacets.goal_categories, ) .filter(SessionFacets.session_id.in_(session_ids_q)) @@ -904,6 +912,47 @@ def get_pattern_sharing( session_tools[tu.session_id].append(tu.tool_name) session_tool_count[tu.session_id] += tu.call_count + session_workflows = { + row.session_id: { + "archetype": row.archetype, + "label": row.label, + "steps": list(row.steps or []), + } + for row in ( + db.query( + SessionWorkflowProfile.session_id, + SessionWorkflowProfile.archetype, + SessionWorkflowProfile.label, + SessionWorkflowProfile.steps, + ) + .filter(SessionWorkflowProfile.session_id.in_(session_ids_q)) + .all() + ) + } + + session_costs: dict[str, float] = defaultdict(float) + model_rows = ( + db.query( + ModelUsage.session_id, + ModelUsage.model_name, + func.sum(ModelUsage.input_tokens).label("input_tokens"), + func.sum(ModelUsage.output_tokens).label("output_tokens"), + func.sum(ModelUsage.cache_read_tokens).label("cache_read_tokens"), + func.sum(ModelUsage.cache_creation_tokens).label("cache_creation_tokens"), + ) + .filter(ModelUsage.session_id.in_(session_ids_q)) + .group_by(ModelUsage.session_id, ModelUsage.model_name) + .all() + ) + for row in model_rows: + session_costs[row.session_id] += estimate_cost( + row.model_name, + row.input_tokens or 0, + row.output_tokens or 0, + row.cache_read_tokens or 0, + row.cache_creation_tokens or 0, + ) + def _make_approach(sid: str) -> EngineerApproach: s = session_map[sid] eid = session_to_engineer[sid] @@ -1027,6 +1076,14 @@ def _build_pattern( return PatternSharingResponse( patterns=patterns, bright_spots=_derive_bright_spots(patterns), + exemplar_sessions=_derive_exemplar_sessions( + patterns, + session_map=session_map, + session_facets=session_facets, + session_tools=session_tools, + session_workflows=session_workflows, + session_costs=session_costs, + ), total_clusters_found=len(patterns), sessions_analyzed=len(sessions), ) @@ -1081,6 +1138,143 @@ def _derive_bright_spots(patterns: list[SharedPattern], limit: int = 3) -> list[ return bright_spots +def _derive_exemplar_sessions( + patterns: list[SharedPattern], + *, + session_map: dict[str, SessionModel], + session_facets: dict[str, object], + session_tools: dict[str, list[str]], + session_workflows: dict[str, dict[str, object]], + session_costs: dict[str, float], + limit: int = 6, +) -> list[ExemplarSession]: + cluster_type_order = { + "session_type": 0, + "goal_category": 1, + "project": 2, + } + buckets: dict[str, dict[str, object]] = {} + + for pattern in patterns: + exemplar = pattern.best_approach + if exemplar is None: + continue + + bucket = buckets.setdefault( + exemplar.session_id, + { + "approach": exemplar, + "linked_patterns": [], + "supporting_session_count": 0, + "supporting_engineer_count": 0, + "success_rate": None, + }, + ) + linked_patterns = bucket["linked_patterns"] + assert isinstance(linked_patterns, list) + linked_patterns.append( + ExemplarPatternReference( + cluster_id=pattern.cluster_id, + 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, + ) + ) + bucket["supporting_session_count"] = max( + int(bucket["supporting_session_count"]), + pattern.session_count, + ) + bucket["supporting_engineer_count"] = max( + int(bucket["supporting_engineer_count"]), + pattern.engineer_count, + ) + current_success_rate = bucket["success_rate"] + if current_success_rate is None or ( + pattern.success_rate is not None and pattern.success_rate > current_success_rate + ): + bucket["success_rate"] = pattern.success_rate + + exemplars: list[ExemplarSession] = [] + for session_id, bucket in buckets.items(): + approach = bucket["approach"] + assert isinstance(approach, EngineerApproach) + session = session_map[session_id] + facet = session_facets.get(session_id) + workflow = session_workflows.get(session_id, {}) + linked_patterns = bucket["linked_patterns"] + assert isinstance(linked_patterns, list) + linked_patterns.sort( + key=lambda item: ( + cluster_type_order.get(item.cluster_type, 99), + -item.engineer_count, + -item.session_count, + -(item.success_rate or 0.0), + item.cluster_label, + ) + ) + primary_pattern = linked_patterns[0] + related_pattern_count = max(len(linked_patterns) - 1, 0) + linked_pattern_phrase = ( + " It also anchors " + f"{related_pattern_count} related pattern" + f"{'' if related_pattern_count == 1 else 's'}." + if related_pattern_count > 0 + else "" + ) + why_selected = ( + f"Chosen as the fastest successful example for {primary_pattern.cluster_label}." + f"{linked_pattern_phrase}" + ) + + title = str(workflow.get("label") or primary_pattern.cluster_label) + summary = ( + f"Backed by {bucket['supporting_engineer_count']} engineers across " + f"{bucket['supporting_session_count']} sessions." + ) + session_summary = getattr(facet, "brief_summary", None) or session.summary + exemplars.append( + ExemplarSession( + exemplar_id=f"exemplar:{session_id}", + title=title, + summary=summary, + why_selected=why_selected, + session_id=session_id, + engineer_id=approach.engineer_id, + engineer_name=approach.name, + project_name=session.project_name, + outcome=approach.outcome, + helpfulness=getattr(facet, "agent_helpfulness", None), + session_summary=session_summary, + duration_seconds=session.duration_seconds, + estimated_cost=( + round(session_costs[session_id], 4) if session_id in session_costs else None + ), + tools_used=sorted(set(session_tools.get(session_id, []))), + workflow_archetype=workflow.get("archetype"), + workflow_fingerprint=workflow.get("label"), + workflow_steps=list(workflow.get("steps") or []), + supporting_session_count=int(bucket["supporting_session_count"]), + supporting_engineer_count=int(bucket["supporting_engineer_count"]), + supporting_pattern_count=len(linked_patterns), + success_rate=bucket["success_rate"], + linked_patterns=linked_patterns[:3], + ) + ) + + exemplars.sort( + key=lambda exemplar: ( + -(exemplar.success_rate or 0.0), + -exemplar.supporting_engineer_count, + -exemplar.supporting_session_count, + exemplar.estimated_cost if exemplar.estimated_cost is not None else float("inf"), + exemplar.title, + ) + ) + return exemplars[:limit] + + # --------------------------------------------------------------------------- # Onboarding Acceleration # --------------------------------------------------------------------------- diff --git a/tests/test_growth.py b/tests/test_growth.py index 54147f5..32de455 100644 --- a/tests/test_growth.py +++ b/tests/test_growth.py @@ -4,7 +4,15 @@ import bcrypt -from primer.common.models import Engineer, Session, SessionFacets, Team, ToolUsage +from primer.common.models import ( + Engineer, + ModelUsage, + Session, + SessionFacets, + SessionWorkflowProfile, + Team, + ToolUsage, +) def _create_session(db_session, engineer, **kwargs): @@ -320,6 +328,83 @@ def test_bright_spots_surface_exemplar_sessions_for_strong_patterns( assert bright_spot["exemplar_session_id"] == session_ids[1] assert bright_spot["exemplar_tools"] == ["Edit", "Read"] + def test_pattern_sharing_includes_workflow_aware_exemplar_library_entries( + 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, + summary="Debugged the auth flow and verified the fix.", + ) + session_ids.append(session.id) + db_session.add( + SessionFacets( + session_id=session.id, + session_type="debugging", + outcome="success", + brief_summary="Reproduced and fixed the auth failure.", + goal_categories=["auth"], + agent_helpfulness="very_helpful", + ) + ) + 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.add( + SessionWorkflowProfile( + session_id=session_ids[1], + fingerprint_id="debugging::read+edit+execute+fix", + label="debugging: read -> edit -> execute -> fix", + steps=["read", "edit", "execute", "fix"], + archetype="debugging", + archetype_source="session_type", + top_tools=["Read", "Edit"], + ) + ) + db_session.add( + ModelUsage( + session_id=session_ids[1], + model_name="claude-sonnet-4", + input_tokens=1000, + output_tokens=500, + cache_read_tokens=0, + cache_creation_tokens=0, + ) + ) + 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() + + exemplar = next( + item for item in data["exemplar_sessions"] if item["session_id"] == session_ids[1] + ) + assert exemplar["title"] == "debugging: read -> edit -> execute -> fix" + assert exemplar["workflow_archetype"] == "debugging" + assert exemplar["workflow_fingerprint"] == "debugging: read -> edit -> execute -> fix" + assert exemplar["workflow_steps"] == ["read", "edit", "execute", "fix"] + assert exemplar["supporting_engineer_count"] == 3 + assert exemplar["supporting_session_count"] == 3 + assert exemplar["supporting_pattern_count"] == 2 + assert exemplar["linked_patterns"][0]["cluster_label"] == "debugging on primer" + assert exemplar["linked_patterns"][1]["cluster_label"] == "auth" + assert exemplar["estimated_cost"] == 0.0105 + assert exemplar["tools_used"] == ["Edit", "Read"] + assert exemplar["session_summary"] == "Reproduced and fixed the auth failure." + def test_best_approach_treats_legacy_success_as_success( self, client, db_session, admin_headers ):