diff --git a/app/components/evaluations/CategoryMetricsTable.tsx b/app/components/evaluations/CategoryMetricsTable.tsx index cb5b9d6..a154dfc 100644 --- a/app/components/evaluations/CategoryMetricsTable.tsx +++ b/app/components/evaluations/CategoryMetricsTable.tsx @@ -1,6 +1,7 @@ "use client"; import { CategoryMetric } from "@/app/lib/types/evaluation"; +import VerdictBadge from "./VerdictBadge"; interface CategoryMetricsTableProps { categoryMetrics: CategoryMetric[]; @@ -60,7 +61,13 @@ export default function CategoryMetricsTable({ {formatScore(row.avg_cosine)} - {formatScore(row.avg_correctness)} + + {formatScore(row.avg_correctness)} + + ))} diff --git a/app/components/evaluations/DetailedResultsTable.tsx b/app/components/evaluations/DetailedResultsTable.tsx index 8d6a9a2..adeea37 100644 --- a/app/components/evaluations/DetailedResultsTable.tsx +++ b/app/components/evaluations/DetailedResultsTable.tsx @@ -15,7 +15,10 @@ import { } from "@/app/lib/utils/evaluation"; import { formatScoreValue, getScoreByName } from "@/app/lib/utils"; import { InfoTooltip } from "@/app/components/ui"; -import { GroupedResultsTable } from "@/app/components/evaluations"; +import { + GroupedResultsTable, + VerdictBadge, +} from "@/app/components/evaluations"; import { MarkdownContent } from "@/app/components/chat"; interface DetailedResultsTableProps { @@ -202,6 +205,11 @@ export default function DetailedResultsTable({ > {value} + {score?.comment && ( )} diff --git a/app/components/evaluations/GroupedResultsTable.tsx b/app/components/evaluations/GroupedResultsTable.tsx index 8a90570..3f9ba00 100644 --- a/app/components/evaluations/GroupedResultsTable.tsx +++ b/app/components/evaluations/GroupedResultsTable.tsx @@ -8,6 +8,7 @@ import { Fragment } from "react"; import { TraceScore, GroupedTraceItem } from "@/app/lib/types/evaluation"; import { formatScoreValue } from "@/app/lib/utils"; import { InfoTooltip } from "@/app/components/ui"; +import VerdictBadge from "./VerdictBadge"; export default function GroupedResultsTable({ traces, }: { @@ -206,6 +207,11 @@ export default function GroupedResultsTable({ > {value} + {score?.comment && ( )} diff --git a/app/components/evaluations/MetricsOverview.tsx b/app/components/evaluations/MetricsOverview.tsx index b27a11c..537af6d 100644 --- a/app/components/evaluations/MetricsOverview.tsx +++ b/app/components/evaluations/MetricsOverview.tsx @@ -3,6 +3,7 @@ import type { EvalJob, SummaryScore } from "@/app/lib/types/evaluation"; import { Button } from "@/app/components/ui"; import { RefreshIcon, WarningTriangleIcon } from "@/app/components/icons"; +import VerdictBadge from "./VerdictBadge"; interface MetricsOverviewProps { job: EvalJob; @@ -57,8 +58,15 @@ export default function MetricsOverview({ key={summary.name} className="rounded-lg px-6 py-5 text-center flex-1 min-w-[180px] relative bg-bg-primary shadow-sm" > -
- {summary.name} +
+ + {summary.name} + +
{summary.avg !== undefined ? summary.avg.toFixed(3) : "N/A"} diff --git a/app/components/evaluations/ScoreDisplay.tsx b/app/components/evaluations/ScoreDisplay.tsx index 68efa33..d0eddec 100644 --- a/app/components/evaluations/ScoreDisplay.tsx +++ b/app/components/evaluations/ScoreDisplay.tsx @@ -7,6 +7,7 @@ import type { ScoreObject } from "@/app/lib/types/evaluation"; import { hasSummaryScores } from "@/app/lib/utils/evaluation"; +import VerdictBadge from "./VerdictBadge"; interface ScoreDisplayProps { score: ScoreObject | null; @@ -75,6 +76,11 @@ export default function ScoreDisplay({ {summary.name}: {value} {std !== null && ±{std}} + ); })} diff --git a/app/components/evaluations/VerdictBadge.tsx b/app/components/evaluations/VerdictBadge.tsx new file mode 100644 index 0000000..166fc2b --- /dev/null +++ b/app/components/evaluations/VerdictBadge.tsx @@ -0,0 +1,41 @@ +"use client"; + +import { + getVerdictBand, + VERDICT_LABELS, + VerdictBand, +} from "@/app/lib/utils/evaluation"; + +interface VerdictBadgeProps { + value: number | string | null | undefined; + dataType?: "NUMERIC" | "CATEGORICAL"; + name?: string; + size?: "sm" | "md"; +} + +const VERDICT_CLASSES: Record = { + good: "bg-status-success-bg text-status-success-text", + needs_refinement: "bg-status-warning-bg text-status-warning-text", + needs_improvement: "bg-status-error-bg text-status-error-text", +}; + +export default function VerdictBadge({ + value, + dataType, + name, + size = "sm", +}: VerdictBadgeProps) { + const band = getVerdictBand(value, dataType, name); + if (!band) return null; + + const padding = + size === "md" ? "px-2.5 py-1 text-xs" : "px-2 py-0.5 text-[10px]"; + + return ( + + {VERDICT_LABELS[band]} + + ); +} diff --git a/app/components/evaluations/index.ts b/app/components/evaluations/index.ts index 6f2fa03..96aff05 100644 --- a/app/components/evaluations/index.ts +++ b/app/components/evaluations/index.ts @@ -13,4 +13,5 @@ export { default as MetricsOverview } from "./MetricsOverview"; export { default as RunEvaluationForm } from "./RunEvaluationForm"; export { default as RunModeBadge } from "./RunModeBadge"; export { default as ScoreDisplay } from "./ScoreDisplay"; +export { default as VerdictBadge } from "./VerdictBadge"; export { default as ViewDatasetModal } from "./ViewDatasetModal"; diff --git a/app/lib/utils/evaluation.ts b/app/lib/utils/evaluation.ts index 1403499..8c62b57 100644 --- a/app/lib/utils/evaluation.ts +++ b/app/lib/utils/evaluation.ts @@ -8,6 +8,36 @@ import type { TraceItem, } from "@/app/lib/types/evaluation"; +export type VerdictBand = "good" | "needs_refinement" | "needs_improvement"; + +export const VERDICT_LABELS: Record = { + good: "Good", + needs_refinement: "Needs Refinement", + needs_improvement: "Needs Improvement", +}; + +/** + * Judge-metric scores are banded 0-5: Good (4-5), Needs Refinement (2-3), + * Needs Improvement (0-1). Cosine scores and categorical/N/A entries carry + * no verdict, so this returns null for them. + */ +export function getVerdictBand( + value: number | string | null | undefined, + dataType: "NUMERIC" | "CATEGORICAL" | undefined, + name?: string, +): VerdictBand | null { + if (dataType === "CATEGORICAL") return null; + if (name && /cosine/i.test(name)) return null; + if (value === null || value === undefined) return null; + + const numValue = Number(value); + if (!Number.isFinite(numValue)) return null; + + if (numValue >= 4) return "good"; + if (numValue >= 2) return "needs_refinement"; + return "needs_improvement"; +} + export function hasSummaryScores( score: ScoreObject | null | undefined, ): score is NewScoreObjectV2 | BasicScoreObject {