import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/src/components/ui/card"; import { Loader2 } from "lucide-react"; import { useScoreAnalytics } from "../ScoreAnalyticsProvider"; import { MetricCard } from "../charts/MetricCard"; import { SamplingDetailsHoverCard } from "../SamplingDetailsHoverCard"; import { calculateCohensKappa, calculateWeightedF1Score, calculateOverallAgreement, interpretPearsonCorrelation, interpretSpearmanCorrelation, interpretCohensKappa, interpretF1Score, interpretOverallAgreement, interpretMAE, interpretRMSE, } from "@/src/features/score-analytics/lib/statistics-utils"; /** * StatisticsCard - Smart card component for displaying score statistics * * Consumes ScoreAnalyticsProvider context and displays: * - Score 1 stats (always shown) * - Score 2 stats (shown in two-score mode) * - Comparison metrics (shown in two-score mode) * * Handles: * - Loading states * - Empty states * - Single vs two-score modes * - Numeric vs categorical data types */ export function StatisticsCard() { const { data, isLoading, params } = useScoreAnalytics(); // Loading state if (isLoading) { return ( Statistics Loading statistics... ); } // No data state if (!data) { return ( Statistics No data available Select a score to view statistics ); } // Extract data from context const { statistics, metadata } = data; const { dataType } = metadata; const { score1, score2 } = params; // Check if Cartesian product occurred (matched count exceeds both individual counts) const hasCartesianProduct = statistics.comparison && statistics.comparison.matchedCount > statistics.score1.total && statistics.score2 && statistics.comparison.matchedCount > statistics.score2.total; // Determine what to show const showScore1Data = statistics.score1.total > 0; const showScore2Data = statistics.score2 !== null; const showComparisonMetrics = statistics.comparison !== null; // Always show Score 2 and Comparison sections once score1 is selected // to set user expectations about what information will be available const showScore2Section = true; // Always show when on this page const showComparisonSection = true; // Always show when on this page // Calculate categorical metrics if available const cohensKappa = showComparisonMetrics && statistics.comparison?.confusionMatrix ? calculateCohensKappa(statistics.comparison.confusionMatrix) : null; const f1Score = showComparisonMetrics && statistics.comparison?.confusionMatrix ? calculateWeightedF1Score(statistics.comparison.confusionMatrix) : null; const overallAgreement = showComparisonMetrics && statistics.comparison?.confusionMatrix ? calculateOverallAgreement(statistics.comparison.confusionMatrix) : null; return ( Statistics {data.samplingMetadata.isSampled && ( )} {score2 ? `${score1.name} vs ${score2.name}` : `${score1.name} - Select a second score for comparison`} {/* Section 1: Score 1 Data */}

{score1.name} ({score1.source})

{dataType === "NUMERIC" ? (
) : (
)}
{/* Section 2: Score 2 Data - Always show to set expectations */} {showScore2Section && (

{score2?.name ?? "Score 2"} {score2?.source ? ` (${score2.source})` : ""}

{dataType === "NUMERIC" ? (
) : (
)}
)} {/* Section 3: Comparison Metrics - Always show to set expectations */} {showComparisonSection && (

Comparison

{dataType === "NUMERIC" ? (
{/* First row: Matched, Pearson, Spearman */}

Matched count exceeds individual score counts due to Cartesian product

This occurs when multiple scores of the same name/source exist on a single attachment point (trace/observation/session/run). Each combination creates a match.

Example: If one trace has 2 "gpt4" scores and 3 "gemini" scores, this creates 6 matched pairs (2 × 3 = 6).

), } : undefined } isContext isPlaceholder={!showComparisonMetrics} />
{/* Second row: Empty, MAE, RMSE */}
) : (
{/* First row: Matched, Agreement */}

Matched count exceeds individual score counts due to Cartesian product

This occurs when multiple scores of the same name/source exist on a single attachment point (trace/observation/session/run). Each combination creates a match.

Example: If one trace has 2 "gpt4" scores and 3 "gemini" scores, this creates 6 matched pairs (2 × 3 = 6).

), } : undefined } isContext isPlaceholder={!showComparisonMetrics} />
{/* Second row: Empty, Cohen's κ, F1 Score */}
)}
)}
); }