Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion apps/web/app/api/outliers/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ function readFilterParams(url: URL) {
fast: url.searchParams.get("fast") ?? undefined,
from: url.searchParams.get("from") ?? undefined,
to: url.searchParams.get("to") ?? undefined,
benchmark: url.searchParams.get("benchmark") ?? undefined,
};
}

Expand All @@ -28,7 +29,10 @@ export async function GET(request: Request) {
}

const filteredIndex = applyIndexFilters(index, filters);
const rows = await buildOutlierExplorerRows(filteredIndex);
const benchmarkId = (filters.benchmark ?? "").trim();
const rows = await buildOutlierExplorerRows(filteredIndex, {
benchmarkId: benchmarkId || undefined,
});

if (format === "csv") {
const csv = outliersToCsv(rows);
Expand Down
7 changes: 5 additions & 2 deletions apps/web/app/benchmarks/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ import {
buildBenchmarkIndexLookup,
resolveIndexedModeLabel,
} from "../../../lib/benchmarkIndexLookup";
import { buildBenchmarkOutlierLookup } from "../../../lib/outlierLookup";
import {
buildBenchmarkOutlierLookup,
outliersBenchmarkHref,
} from "../../../lib/outlierLookup";
import { questionHubHref } from "../../../lib/questionGroups";
import {
extractBenchmarkSummaryDisplay,
Expand Down Expand Up @@ -167,7 +170,7 @@ export default async function BenchmarkDetailPage({
</Link>
{outlierCount > 0 ? (
<Link
href={`/outliers?q=${encodeURIComponent(benchmark.question)}`}
href={outliersBenchmarkHref(benchmark.id)}
className="button secondary"
>
Outlier runs ({outlierCount})
Expand Down
30 changes: 27 additions & 3 deletions apps/web/app/outliers/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type OutliersSearchParams = {
fast?: string;
from?: string;
to?: string;
benchmark?: string;
};

export default async function OutliersPage({
Expand Down Expand Up @@ -47,7 +48,10 @@ export default async function OutliersPage({

const { models, presets } = collectIndexFacets(rawIndex);
const index = applyIndexFilters(rawIndex, params);
const rows = await buildOutlierExplorerRows(index);
const benchmarkId = (params.benchmark ?? "").trim();
const rows = await buildOutlierExplorerRows(index, {
benchmarkId: benchmarkId || undefined,
});

return (
<section className="stack">
Expand Down Expand Up @@ -78,15 +82,33 @@ export default async function OutliersPage({
params={params}
totalRuns={rawIndex.runs.length}
filteredRuns={index.runs.length}
preserveKeys={benchmarkId ? ["benchmark"] : []}
/>

{benchmarkId ? (
<div className="card">
<p className="muted" style={{ margin: 0 }}>
Showing outliers for benchmark{" "}
<Link href={`/benchmarks/${benchmarkId}`}>
<code>{benchmarkId}</code>
</Link>
.{" "}
<Link href="/outliers" className="button secondary">
Clear benchmark filter
</Link>
</p>
</div>
) : null}

{rows.length > 0 ? (
<div
className="page-actions"
style={{ display: "flex", gap: 10, flexWrap: "wrap" }}
>
<a
href={`/api/outliers${buildQueryString(params, {})}`}
href={`/api/outliers${buildQueryString(params, {
benchmark: benchmarkId || undefined,
})}`}
className="button secondary"
target="_blank"
rel="noopener noreferrer"
Expand All @@ -95,7 +117,9 @@ export default async function OutliersPage({
Export JSON
</a>
<a
href={`/api/outliers${buildQueryString(params, {})}&format=csv`}
href={`/api/outliers${buildQueryString(params, {
benchmark: benchmarkId || undefined,
})}&format=csv`}
className="button secondary"
download="outlier-runs.csv"
>
Expand Down
156 changes: 155 additions & 1 deletion apps/web/app/runs/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@ import { RunMetricsSummary } from "../../../components/RunMetricsSummary";
import { ConsensusSummary } from "../../../components/ConsensusSummary";
import { CritiqueBreakdown } from "../../../components/CritiqueBreakdown";
import { CritiqueByAgent } from "../../../components/CritiqueByAgent";
import { MetricCard } from "../../../components/MetricCard";
import { extractConsensusSummary } from "../../../lib/consensusSummary";
import { extractCritiqueByType } from "../../../lib/critiqueBreakdown";
import { extractCritiqueByAgent } from "../../../lib/critiqueByAgent";
import { extractCritiqueNotesFromRun } from "../../../lib/critiqueNotes";
import { buildDriftCompareHref } from "../../../lib/confidenceDrift";
import { DownloadArtifactLink } from "../../../components/DownloadArtifactLink";
import { CopyPageLink } from "../../../components/CopyPageLink";
import { CopyTextButton } from "../../../components/CopyTextButton";
import { summarizeRun } from "../../../lib/runCompare";
import { buildCompareSuggestions } from "../../../lib/compareSuggestions";
import { questionHubHref } from "../../../lib/questionGroups";
import { questionHubHref, listQuestionInsightLinks } from "../../../lib/questionGroups";
import {
buildRunStepTiming,
formatDurationMs,
Expand Down Expand Up @@ -76,6 +79,10 @@ export default async function RunTracePage({
const consensusSummary = extractConsensusSummary(run);
const critiqueByType = extractCritiqueByType(run);
const critiqueByAgent = extractCritiqueByAgent(run);
const critiqueNotes = extractCritiqueNotesFromRun(run);
const indexedRun = index?.runs.find((entry) => entry.id === run.id);
const insightLinks = listQuestionInsightLinks(run.question);
const driftCompareHref = buildDriftCompareHref(allRuns, run.id);
const { schemaVersion, pipelineVersion } = run.metadata;

return (
Expand Down Expand Up @@ -209,6 +216,108 @@ export default async function RunTracePage({
<RunMetricsSummary summary={metricsSummary} />
</div>

{indexedRun ? (
<div className="card">
<h2 style={{ marginTop: 0 }}>Confidence drift</h2>
<p className="small muted" style={{ marginBottom: "1rem" }}>
Stage-to-stage confidence deltas from the analysis index
— compare with peers on the same question.
</p>
<div className="grid-4">
<MetricCard
label="Solver→revision Δ"
value={
indexedRun.confidence.solverToRevisionDelta !=
null
? indexedRun.confidence.solverToRevisionDelta.toFixed(
3,
)
: "—"
}
helpKey="solverToRevisionDelta"
/>
<MetricCard
label="Revision→synth Δ"
value={
indexedRun.confidence
.revisionToSynthesizerDelta != null
? indexedRun.confidence.revisionToSynthesizerDelta.toFixed(
3,
)
: "—"
}
helpKey="revisionToSynthesizerDelta"
/>
<MetricCard
label="Calibrated−synth Δ"
value={
indexedRun.confidence.calibratedAdjusted !=
null &&
indexedRun.confidence.synthesizer != null
? (
indexedRun.confidence
.calibratedAdjusted -
indexedRun.confidence.synthesizer
).toFixed(3)
: "—"
}
helpKey="calibratedMinusSynthDelta"
/>
<MetricCard
label="Evidence risk"
value={
indexedRun.research?.evidenceRiskLevel != null
? indexedRun.research.evidenceRiskLevel.toFixed(
1,
)
: "—"
}
helpKey="evidenceRiskLevel"
/>
</div>
<div
className="page-actions"
style={{
display: "flex",
gap: 10,
flexWrap: "wrap",
marginTop: 12,
}}
>
<Link href="/drift" className="button secondary">
Drift explorer
</Link>
<Link
href={driftCompareHref}
className="button secondary"
>
Compare drift peer
</Link>
</div>
</div>
) : null}

<div className="card">
<h2 style={{ marginTop: 0 }}>Question insight shortcuts</h2>
<p className="small muted" style={{ marginBottom: "1rem" }}>
Open filtered insight pages scoped to this research question.
</p>
<div
className="page-actions"
style={{ display: "flex", gap: 10, flexWrap: "wrap" }}
>
{insightLinks.map((link) => (
<Link
key={link.page}
href={link.href}
className="button secondary"
>
{link.label}
</Link>
))}
</div>
</div>

{stepTimingSummary.timedStepCount > 0 ? (
<div className="card">
<h2 style={{ marginTop: 0 }}>Step timing</h2>
Expand Down Expand Up @@ -368,6 +477,51 @@ export default async function RunTracePage({
<CritiqueBreakdown entries={critiqueByType} />
</div>

{critiqueNotes.length > 0 ? (
<div className="card">
<h2 style={{ marginTop: 0 }}>Critique notes</h2>
<p className="small muted" style={{ marginBottom: "1rem" }}>
Verbatim issue notes from skeptic and red team critique
steps in this trace.
</p>
<ResponsiveTable
columns={[
{ key: "agentName", label: "Agent" },
{
key: "severity",
label: "Severity",
helpKey: "maxSeverity",
},
{
key: "type",
label: "Type",
render: (row) => (
<Link
href={`/issues?type=${encodeURIComponent(
(row as { type: string }).type,
)}&q=${encodeURIComponent(run.question)}`}
>
{(row as { type: string }).type}
</Link>
),
},
{
key: "note",
label: "Note",
cellClass: "cell-question",
},
]}
data={critiqueNotes.map((note, idx) => ({
...note,
rowKey: `${note.agentName}-${note.type}-${idx}`,
}))}
getRowId={(row) =>
(row as { rowKey: string }).rowKey
}
/>
</div>
) : null}

<div className="card trace-final-answer">
<div
style={{
Expand Down
30 changes: 30 additions & 0 deletions apps/web/app/runs/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,34 @@ export default async function RunsPage({
: value.toFixed(2);
},
},
{
key: "evidenceRisk",
label: "Evidence risk",
helpKey: "evidenceRiskLevel",
hideOnMobile: true,
render: (row: Record<string, unknown>) => {
const value = (
row as { evidenceRisk?: number }
).evidenceRisk;
return value == null
? "—"
: value.toFixed(1);
},
},
{
key: "solverDrift",
label: "Solver→rev Δ",
helpKey: "solverToRevisionDelta",
hideOnMobile: true,
render: (row: Record<string, unknown>) => {
const value = (
row as { solverDrift?: number }
).solverDrift;
return value == null
? "—"
: value.toFixed(2);
},
},
{
key: "coherence",
label: "Coherence",
Expand Down Expand Up @@ -428,6 +456,8 @@ export default async function RunsPage({
issues: indexed?.issueCount,
avgSeverity: indexed?.avgSeverity,
solverConf: indexed?.solverConfidence,
evidenceRisk: indexed?.evidenceRiskLevel,
solverDrift: indexed?.solverToRevisionDelta,
coherence: indexed?.coherence,
factualRisk: indexed?.factualRisk,
};
Expand Down
14 changes: 12 additions & 2 deletions apps/web/lib/indexRunLookup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,21 @@ function sampleIndex(): AnalysisIndex {
fastMode: false,
finalAnswerPreview: "A",
stepCount: 10,
confidence: { solver: 0.8 },
confidence: {
solver: 0.8,
solverToRevisionDelta: -0.12,
revisionToSynthesizerDelta: 0.05,
},
critique: {
issueCount: 5,
maxSeverity: 4,
avgSeverity: 3.2,
byType: { factual: 2 },
},
research: { evidenceRiskLevel: 3 },
research: {
evidenceRiskLevel: 3,
topCounterfactualFailureMode: "missing evidence",
},
quality: { coherence: 4.1, factualRisk: 2.5 },
},
],
Expand Down Expand Up @@ -51,9 +58,12 @@ describe("buildIndexRunLookup", () => {
maxSeverity: 4,
avgSeverity: 3.2,
solverConfidence: 0.8,
solverToRevisionDelta: -0.12,
revisionToSynthesizerDelta: 0.05,
evidenceRiskLevel: 3,
coherence: 4.1,
factualRisk: 2.5,
topCounterfactualFailureMode: "missing evidence",
});
expect(lookup.get("missing")).toBeUndefined();
});
Expand Down
Loading
Loading