AgentGraph / frontend /src /components /features /traces /CausalResults.tsx
wu981526092's picture
🚀 Deploy AgentGraph: Complete agent monitoring and knowledge graph system
c2ea5ed
import React, { useState, useCallback } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Progress } from "@/components/ui/progress";
import { Button } from "@/components/ui/button";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import {
BarChart,
Bar,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
} from "recharts";
import {
TrendingUp,
Network,
Target,
GitBranch,
AlertTriangle,
Search,
BarChart3,
ChevronDown,
ChevronUp,
Info,
Brain,
Shuffle,
} from "lucide-react";
interface CausalResult {
id: number;
causal_score: number;
analysis_method: string;
created_at: string;
updated_at: string;
perturbation_set_id: string;
metadata: {
timestamp: string;
method_specific_metadata: Record<string, any>;
};
raw_analysis: {
scores: Record<string, any>;
metadata: Record<string, any>;
};
}
interface CausalResultsData {
causal_results: CausalResult[];
causal_results_by_set: Record<string, CausalResult[]>;
perturbation_set_types?: Record<string, string>;
perturbation_set_metadata?: Record<
string,
{
created_at: string | null;
test_metadata: Record<string, any>;
}
>;
summary: {
total_causal_relations: number;
total_perturbation_sets: number;
};
}
interface KnowledgeGraphData {
entities: Array<{ id: string; name: string; type: string }>;
relations: Array<{
id: string;
type: string;
source: string;
target: string;
}>;
}
interface CausalResultsProps {
data: CausalResultsData;
knowledgeGraphData?: KnowledgeGraphData;
}
const methodMetadata = {
graph: {
name: "Graph Analysis",
description:
"Shows which parts of your agent system have the strongest causal influence on overall performance",
explanation:
"This method analyzes your agent system like a network to find which components (agents, tools, tasks) most strongly affect the final outcomes. Higher scores mean stronger causal influence.",
icon: Network,
color: "blue",
metrics: ["ACE", "Shapley"],
},
component: {
name: "Component Analysis",
description:
"Uses machine learning to rank which components matter most for system performance",
explanation:
"Think of this as a 'feature importance' analysis for your agent system. It trains a model to predict outcomes and tells you which components are most critical for success.",
icon: BarChart3,
color: "green",
metrics: ["Feature_Importance", "Model_Metrics", "Key_Components"],
},
dowhy: {
name: "DoWhy Analysis",
description: "Rigorous causal inference using Microsoft's DoWhy framework",
icon: Target,
color: "purple",
metrics: [
"Effect_Estimate",
"Refutation_Results",
"Interaction_Effects",
"Confounders",
],
},
confounder: {
name: "Confounder Detection",
description:
"Detects confounding variables that create spurious correlations",
icon: AlertTriangle,
color: "orange",
metrics: ["Confounders", "Impact_Analysis", "Summary"],
},
mscd: {
name: "Multi-Signal Confounder Detection",
description: "Advanced confounder detection using multiple signal types",
icon: Search,
color: "red",
metrics: ["Confounders", "Method_Results", "Summary"],
},
ate: {
name: "Average Treatment Effect",
description:
"Measures direct causal effects and Granger causality between components",
icon: GitBranch,
color: "indigo",
metrics: ["Effect_Strengths", "Granger_Results", "ATE_Results"],
},
};
const CausalResults: React.FC<CausalResultsProps> = ({
data,
knowledgeGraphData,
}) => {
const [selectedSet, setSelectedSet] = useState<string | null>(null);
const [expandedMethods, setExpandedMethods] = useState<Set<string>>(
new Set()
);
// Helper function to convert ID to user-friendly name
const getDisplayName = useCallback(
(id: string): string => {
if (!knowledgeGraphData) {
// Fallback: clean up the ID for better readability
return id.replace(/^(entity_|relation_)/, "").replace(/_/g, " ");
}
// Try to find the entity first
const entity = knowledgeGraphData.entities.find((e) => e.id === id);
if (entity && entity.name) {
return entity.name;
}
// Try to find the relation
const relation = knowledgeGraphData.relations.find((r) => r.id === id);
if (relation) {
// For relations, show the type as it's more meaningful
return (
relation.type ||
relation.id.replace(/^relation_/, "").replace(/_/g, " ")
);
}
// Enhanced fallback - clean up common ID patterns
return id
.replace(/^(entity_|relation_)/, "")
.replace(/relation_/g, "")
.replace(/_/g, " ")
.replace(/\b\w/g, (l) => l.toUpperCase()); // Capitalize first letter of each word
},
[knowledgeGraphData]
);
// Helper function to get component type and add appropriate context
const getComponentInfo = useCallback(
(id: string): { name: string; type: string; description: string } => {
if (!knowledgeGraphData) {
const type = id.startsWith("entity_") ? "Entity" : "Relation";
return {
name: getDisplayName(id),
type,
description: `${type}: ${getDisplayName(id)}`,
};
}
const entity = knowledgeGraphData.entities.find((e) => e.id === id);
if (entity) {
return {
name: entity.name || getDisplayName(id),
type: entity.type || "Entity",
description: `${entity.type || "Entity"}: ${
entity.name || getDisplayName(id)
}`,
};
}
const relation = knowledgeGraphData.relations.find((r) => r.id === id);
if (relation) {
const sourceEntity = knowledgeGraphData.entities.find(
(e) => e.id === relation.source
);
const targetEntity = knowledgeGraphData.entities.find(
(e) => e.id === relation.target
);
const relationName = relation.type || getDisplayName(id);
if (sourceEntity && targetEntity) {
return {
name: relationName,
type: "Relation",
description: `${relationName}: ${
sourceEntity.name || sourceEntity.id
}${targetEntity.name || targetEntity.id}`,
};
}
return {
name: relationName,
type: "Relation",
description: `Relation: ${relationName}`,
};
}
// Fallback
const type = id.startsWith("entity_") ? "Entity" : "Relation";
return {
name: getDisplayName(id),
type,
description: `${type}: ${getDisplayName(id)}`,
};
},
[knowledgeGraphData, getDisplayName]
);
// Helper function to get user-friendly name for perturbation set
const getPerturbationSetDisplayName = useCallback(
(setId: string): string => {
if (!data.perturbation_set_types) {
return setId.slice(0, 8) + "...";
}
const testType = data.perturbation_set_types[setId];
if (!testType) {
return setId.slice(0, 8) + "...";
}
// Convert test type to user-friendly name
const friendlyNames: Record<string, string> = {
jailbreak: "Jailbreak Tests",
counterfactual_bias: "Bias Tests",
jailbreaking: "Jailbreak Tests",
counterfactual: "Bias Tests",
bias: "Bias Tests",
safety: "Safety Tests",
robustness: "Robustness Tests",
};
const baseName =
friendlyNames[testType.toLowerCase()] ||
testType.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
// Add timestamp differentiation if metadata is available
if (
data.perturbation_set_metadata &&
data.perturbation_set_metadata[setId]
) {
const metadata = data.perturbation_set_metadata[setId];
if (metadata.created_at) {
try {
const date = new Date(metadata.created_at);
const timeStr = date.toLocaleTimeString("en-US", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
});
const dateStr = date.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
});
return `${baseName} (${dateStr} ${timeStr})`;
} catch (e) {
// If date parsing fails, use short ID
return `${baseName} (${setId.slice(0, 6)})`;
}
}
}
return baseName;
},
[data.perturbation_set_types, data.perturbation_set_metadata]
);
if (!data || !data.causal_results || data.causal_results.length === 0) {
return (
<Card className="border-l-4 border-l-indigo-500">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Brain className="h-5 w-5 text-indigo-600" />
Causal Analysis Results
</CardTitle>
<div className="mt-3 p-4 bg-blue-50 border border-blue-200 rounded-lg">
<div className="flex items-start gap-2">
<Info className="h-4 w-4 text-blue-600 mt-0.5 flex-shrink-0" />
<div className="text-sm text-blue-800">
<p className="font-medium mb-1">
Understanding Causal Analysis
</p>
<p>
This analysis reveals which components in your agent system
actually <strong>cause</strong> better or worse outcomes, not
just correlations. Use these insights to identify the most
impactful parts of your system for optimization.
</p>
</div>
</div>
</div>
</CardHeader>
<CardContent>
<div className="text-center py-8 text-muted-foreground">
<Brain className="h-12 w-12 mx-auto mb-4 opacity-50" />
<h3 className="text-lg font-medium mb-2">
No Causal Analysis Results
</h3>
<p>
No causal analysis data is available for this knowledge graph.
</p>
</div>
</CardContent>
</Card>
);
}
// Get available perturbation sets
const perturbationSets = Object.keys(data.causal_results_by_set || {});
// Filter results based on selected perturbation set
const filteredResults =
selectedSet === null
? data.causal_results
: data.causal_results_by_set[selectedSet] || [];
// Group filtered results by method
const resultsByMethod = filteredResults.reduce((acc, result) => {
if (!acc[result.analysis_method]) {
acc[result.analysis_method] = [];
}
acc[result.analysis_method]!.push(result);
return acc;
}, {} as Record<string, CausalResult[]>);
// Calculate overall statistics from filtered results
const overallStats = {
totalMethods: Object.keys(resultsByMethod).length,
avgCausalScore:
filteredResults.length > 0
? filteredResults.reduce((sum, r) => sum + r.causal_score, 0) /
filteredResults.length
: 0,
maxCausalScore:
filteredResults.length > 0
? Math.max(...filteredResults.map((r) => r.causal_score))
: 0,
activeComponents: new Set(
filteredResults.flatMap((r) =>
Object.keys(r.raw_analysis.scores).flatMap((scoreType) =>
Object.keys(r.raw_analysis.scores[scoreType] || {})
)
)
).size,
};
const toggleMethodExpansion = (method: string) => {
const newExpanded = new Set(expandedMethods);
if (newExpanded.has(method)) {
newExpanded.delete(method);
} else {
newExpanded.add(method);
}
setExpandedMethods(newExpanded);
};
const renderMethodSummary = (method: string, results: CausalResult[]) => {
const metadata = methodMetadata[method as keyof typeof methodMetadata];
if (!metadata) return null;
const IconComponent = metadata.icon;
const latestResult = results[results.length - 1];
if (!latestResult) return null;
const avgScore =
results.reduce((sum, r) => sum + r.causal_score, 0) / results.length;
return (
<Collapsible
key={method}
open={expandedMethods.has(method)}
onOpenChange={() => toggleMethodExpansion(method)}
>
<Card className={`border-l-4 border-l-${metadata.color}-500`}>
<CollapsibleTrigger asChild>
<CardHeader className="cursor-pointer hover:bg-muted/50 transition-colors">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className={`p-2 bg-${metadata.color}-100 rounded-lg`}>
<IconComponent
className={`h-5 w-5 text-${metadata.color}-600`}
/>
</div>
<div>
<CardTitle className="text-lg">{metadata.name}</CardTitle>
<p className="text-sm text-muted-foreground mt-1">
{metadata.description}
</p>
</div>
</div>
<div className="flex items-center gap-3">
<div className="text-right">
<div className="text-2xl font-bold">
{avgScore.toFixed(3)}
</div>
<div className="text-xs text-muted-foreground">
Avg Score
</div>
</div>
<Badge variant="secondary">{results.length} runs</Badge>
{expandedMethods.has(method) ? (
<ChevronUp className="h-4 w-4" />
) : (
<ChevronDown className="h-4 w-4" />
)}
</div>
</div>
</CardHeader>
</CollapsibleTrigger>
<CollapsibleContent>
<CardContent className="pt-0">
{renderMethodDetails(method, latestResult, metadata)}
</CardContent>
</CollapsibleContent>
</Card>
</Collapsible>
);
};
const renderMethodDetails = (
method: string,
result: CausalResult,
_metadata: any
) => {
const scores = result.raw_analysis.scores;
switch (method) {
case "graph":
return renderGraphAnalysis(scores);
case "component":
return renderComponentAnalysis(scores);
case "dowhy":
return renderDoWhyAnalysis(scores);
case "confounder":
case "mscd":
return renderConfounderAnalysis(scores, method);
case "ate":
return renderATEAnalysis(scores);
default:
return renderGenericAnalysis(scores);
}
};
const renderGraphAnalysis = (scores: any) => {
const aceData = Object.entries(scores.ACE || {})
.map(([key, value]) => {
const componentInfo = getComponentInfo(key);
return {
name: componentInfo.name,
value: value as number,
type: componentInfo.type,
description: componentInfo.description,
fullId: key,
};
})
.sort((a, b) => Math.abs(b.value) - Math.abs(a.value))
.slice(0, 10);
const shapleyData = Object.entries(scores.Shapley || {})
.map(([key, value]) => {
const componentInfo = getComponentInfo(key);
return {
name: componentInfo.name,
value: value as number,
type: componentInfo.type,
description: componentInfo.description,
fullId: key,
};
})
.sort((a, b) => Math.abs(b.value) - Math.abs(a.value))
.slice(0, 10);
return (
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* ACE Scores */}
<div>
<h4 className="text-sm font-semibold mb-3 flex items-center gap-2">
<TrendingUp className="h-4 w-4" />
Average Causal Effect (ACE)
</h4>
<div className="h-64">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={aceData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="name"
angle={-45}
textAnchor="end"
height={60}
fontSize={10}
/>
<YAxis fontSize={10} />
<Tooltip
labelFormatter={(name, _payload) => {
const item = aceData.find((d) => d.name === name);
return item ? item.description : `Component: ${name}`;
}}
formatter={(value: number) => [
value.toFixed(4),
"ACE Score",
]}
/>
<Bar dataKey="value" fill="#8884d8" />
</BarChart>
</ResponsiveContainer>
</div>
</div>
{/* Shapley Values */}
<div>
<h4 className="text-sm font-semibold mb-3 flex items-center gap-2">
<Shuffle className="h-4 w-4" />
Shapley Values
</h4>
<div className="h-64">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={shapleyData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="name"
angle={-45}
textAnchor="end"
height={60}
fontSize={10}
/>
<YAxis fontSize={10} />
<Tooltip
labelFormatter={(name, _payload) => {
const item = shapleyData.find((d) => d.name === name);
return item ? item.description : `Component: ${name}`;
}}
formatter={(value: number) => [
value.toFixed(6),
"Shapley Value",
]}
/>
<Bar dataKey="value" fill="#82ca9d" />
</BarChart>
</ResponsiveContainer>
</div>
</div>
</div>
{/* Top Components Summary */}
<div className="mt-4">
<h4 className="text-sm font-semibold mb-3">Top Causal Components</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{aceData.slice(0, 5).map((item, index) => (
<div
key={`ace-${index}`}
className="flex items-center justify-between p-3 bg-muted/50 rounded-lg"
title={item.description}
>
<div className="flex items-center gap-2">
<Badge
variant={item.type === "Entity" ? "default" : "secondary"}
className="text-xs"
>
{item.type}
</Badge>
<span className="text-sm font-medium">{item.name}</span>
</div>
<div className="text-right">
<div
className={`text-sm font-bold ${
item.value >= 0 ? "text-green-600" : "text-red-600"
}`}
>
{item.value >= 0 ? "+" : ""}
{item.value.toFixed(4)}
</div>
<div className="text-xs text-muted-foreground">ACE</div>
</div>
</div>
))}
</div>
</div>
</div>
);
};
const renderComponentAnalysis = (scores: any) => {
const featureImportance = scores.Feature_Importance || {};
const modelMetrics = scores.Model_Metrics || {};
const keyComponents = scores.Key_Components || [];
const importanceData = Object.entries(featureImportance)
.map(([key, value]) => {
const componentInfo = getComponentInfo(key);
return {
name: componentInfo.name,
value: Math.abs(value as number),
direction: (value as number) >= 0 ? "positive" : "negative",
type: componentInfo.type,
description: componentInfo.description,
fullId: key,
};
})
.sort((a, b) => b.value - a.value)
.slice(0, 10);
return (
<div className="space-y-6">
{/* Model Performance Metrics */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Card className="p-4">
<div className="text-center">
<div className="text-2xl font-bold text-blue-600">
{(modelMetrics.r2 || 0).toFixed(3)}
</div>
<div className="text-sm text-muted-foreground">R² Score</div>
<Progress
value={(modelMetrics.r2 || 0) * 100}
className="mt-2 h-2"
/>
</div>
</Card>
<Card className="p-4">
<div className="text-center">
<div className="text-2xl font-bold text-green-600">
{(modelMetrics.rmse || 0).toFixed(4)}
</div>
<div className="text-sm text-muted-foreground">RMSE</div>
</div>
</Card>
<Card className="p-4">
<div className="text-center">
<div className="text-2xl font-bold text-purple-600">
{keyComponents.length}
</div>
<div className="text-sm text-muted-foreground">
Key Components
</div>
</div>
</Card>
</div>
{/* Feature Importance Chart */}
<div>
<h4 className="text-sm font-semibold mb-3 flex items-center gap-2">
<BarChart3 className="h-4 w-4" />
Feature Importance
</h4>
<div className="h-64">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={importanceData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="name"
angle={-45}
textAnchor="end"
height={60}
fontSize={10}
/>
<YAxis fontSize={10} />
<Tooltip
labelFormatter={(name) => `Component: ${name}`}
formatter={(value: number, name, props) => [
`${value.toFixed(4)} (${props.payload.direction})`,
"Importance",
]}
/>
<Bar dataKey="value" fill="#8884d8" />
</BarChart>
</ResponsiveContainer>
</div>
</div>
{/* Key Components List */}
{keyComponents.length > 0 && (
<div>
<h4 className="text-sm font-semibold mb-3">
Key Components Identified
</h4>
<div className="flex flex-wrap gap-2">
{keyComponents.map((component: string, index: number) => {
const componentInfo = getComponentInfo(component);
return (
<Badge
key={index}
variant="outline"
className="text-xs"
title={componentInfo.description}
>
{componentInfo.name}
</Badge>
);
})}
</div>
</div>
)}
</div>
);
};
const renderDoWhyAnalysis = (scores: any) => {
const effectEstimates = scores.Effect_Estimate || {};
const confounders = scores.Confounders || {};
const interactionEffects = scores.Interaction_Effects || {};
const effectData = Object.entries(effectEstimates)
.map(([key, value]) => ({
name: key.replace(/^(entity_|relation_)/, ""),
value: value as number,
type: key.startsWith("entity_") ? "Entity" : "Relation",
}))
.sort((a, b) => Math.abs(b.value) - Math.abs(a.value))
.slice(0, 10);
const confounderCount = Object.values(confounders).flat().length;
const interactionCount = Object.values(interactionEffects).flat().length;
return (
<div className="space-y-6">
{/* Summary Stats */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Card className="p-4">
<div className="text-center">
<div className="text-2xl font-bold text-purple-600">
{Object.keys(effectEstimates).length}
</div>
<div className="text-sm text-muted-foreground">
Effect Estimates
</div>
</div>
</Card>
<Card className="p-4">
<div className="text-center">
<div className="text-2xl font-bold text-orange-600">
{confounderCount}
</div>
<div className="text-sm text-muted-foreground">
Confounders Detected
</div>
</div>
</Card>
<Card className="p-4">
<div className="text-center">
<div className="text-2xl font-bold text-blue-600">
{interactionCount}
</div>
<div className="text-sm text-muted-foreground">
Interaction Effects
</div>
</div>
</Card>
</div>
{/* Effect Estimates Chart */}
<div>
<h4 className="text-sm font-semibold mb-3 flex items-center gap-2">
<Target className="h-4 w-4" />
Causal Effect Estimates
</h4>
<div className="h-64">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={effectData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="name"
angle={-45}
textAnchor="end"
height={60}
fontSize={10}
/>
<YAxis fontSize={10} />
<Tooltip
labelFormatter={(name) => `Component: ${name}`}
formatter={(value: number) => [
value.toFixed(4),
"Effect Size",
]}
/>
<Bar dataKey="value" fill="#8b5cf6" />
</BarChart>
</ResponsiveContainer>
</div>
</div>
{/* Confounders Summary */}
{confounderCount > 0 && (
<div>
<h4 className="text-sm font-semibold mb-3 flex items-center gap-2">
<AlertTriangle className="h-4 w-4" />
Detected Confounders
</h4>
<div className="space-y-2">
{Object.entries(confounders)
.slice(0, 5)
.map(([component, confounderList]: [string, any]) => (
<div
key={component}
className="p-3 bg-orange-50 border border-orange-200 rounded-lg"
>
<div className="font-medium text-sm">
{component.replace(/^(entity_|relation_)/, "")}
</div>
<div className="text-xs text-muted-foreground mt-1">
{Array.isArray(confounderList)
? confounderList.length
: 0}{" "}
confounder(s) detected
</div>
</div>
))}
</div>
</div>
)}
</div>
);
};
const renderConfounderAnalysis = (scores: any, method: string) => {
const confounders = scores.Confounders || {};
const confounderCount = Object.keys(confounders).length;
const affectedComponents = Object.values(confounders).flat().length;
return (
<div className="space-y-6">
{/* Summary */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Card className="p-4">
<div className="text-center">
<div className="text-2xl font-bold text-orange-600">
{confounderCount}
</div>
<div className="text-sm text-muted-foreground">
Confounders Found
</div>
</div>
</Card>
<Card className="p-4">
<div className="text-center">
<div className="text-2xl font-bold text-red-600">
{affectedComponents}
</div>
<div className="text-sm text-muted-foreground">
Affected Components
</div>
</div>
</Card>
</div>
{confounderCount === 0 ? (
<Card className="p-6">
<div className="text-center text-muted-foreground">
<Search className="h-12 w-12 mx-auto mb-4 opacity-50" />
<h3 className="text-lg font-medium mb-2">
No Confounders Detected
</h3>
<p>
The {method === "mscd" ? "multi-signal" : "basic"} analysis
found no significant confounding relationships.
</p>
</div>
</Card>
) : (
<div>
<h4 className="text-sm font-semibold mb-3 flex items-center gap-2">
<AlertTriangle className="h-4 w-4" />
Confounder Relationships
</h4>
<div className="space-y-2">
{Object.entries(confounders).map(
([component, _details]: [string, any]) => (
<div
key={component}
className="p-3 bg-orange-50 border border-orange-200 rounded-lg"
>
<div className="font-medium text-sm">
{component.replace(/^(entity_|relation_)/, "")}
</div>
<div className="text-xs text-muted-foreground mt-1">
Potential confounding detected • Impact analysis available
</div>
</div>
)
)}
</div>
</div>
)}
</div>
);
};
const renderATEAnalysis = (scores: any) => {
const effectStrengths = scores.Effect_Strengths || {};
const ateResults = scores.ATE_Results || {};
const grangerResults = scores.Granger_Results || {};
const strengthData = Object.entries(effectStrengths)
.map(([key, value]) => ({
name: key.replace(/^(entity_|relation_)/, ""),
value: Math.abs(value as number),
direction: (value as number) >= 0 ? "positive" : "negative",
type: key.startsWith("entity_") ? "Entity" : "Relation",
}))
.sort((a, b) => b.value - a.value)
.slice(0, 10);
const significantEffects = Object.entries(ateResults).filter(
([_, result]: [string, any]) => Math.abs(result.ate || 0) > 0.01
).length;
const causalDirections: Record<string, number> = Object.values(
grangerResults
).reduce((acc: Record<string, number>, result: any) => {
const direction = result.causal_direction || "none";
acc[direction] = (acc[direction] || 0) + 1;
return acc;
}, {});
return (
<div className="space-y-6">
{/* Summary Metrics */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Card className="p-4">
<div className="text-center">
<div className="text-2xl font-bold text-indigo-600">
{Object.keys(effectStrengths).length}
</div>
<div className="text-sm text-muted-foreground">
Components Analyzed
</div>
</div>
</Card>
<Card className="p-4">
<div className="text-center">
<div className="text-2xl font-bold text-green-600">
{significantEffects}
</div>
<div className="text-sm text-muted-foreground">
Significant Effects
</div>
</div>
</Card>
<Card className="p-4">
<div className="text-center">
<div className="text-2xl font-bold text-purple-600">
{causalDirections.bidirectional || 0}
</div>
<div className="text-sm text-muted-foreground">
Bidirectional Effects
</div>
</div>
</Card>
</div>
{/* Effect Strengths Chart */}
<div>
<h4 className="text-sm font-semibold mb-3 flex items-center gap-2">
<GitBranch className="h-4 w-4" />
Treatment Effect Strengths
</h4>
<div className="h-64">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={strengthData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="name"
angle={-45}
textAnchor="end"
height={60}
fontSize={10}
/>
<YAxis fontSize={10} />
<Tooltip
labelFormatter={(name) => `Component: ${name}`}
formatter={(value: number, name, props) => [
`${value.toFixed(4)} (${props.payload.direction})`,
"Effect Strength",
]}
/>
<Bar dataKey="value" fill="#8884d8" />
</BarChart>
</ResponsiveContainer>
</div>
</div>
{/* Causal Direction Distribution */}
{Object.keys(causalDirections).length > 0 && (
<div>
<h4 className="text-sm font-semibold mb-3 flex items-center gap-2">
<Shuffle className="h-4 w-4" />
Causal Direction Distribution
</h4>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{Object.entries(causalDirections).map(
([direction, count]: [string, any]) => (
<div
key={direction}
className="p-3 bg-muted/50 rounded-lg text-center"
>
<div className="text-2xl font-bold">{count}</div>
<div className="text-sm text-muted-foreground capitalize">
{direction}
</div>
</div>
)
)}
</div>
</div>
)}
</div>
);
};
const renderGenericAnalysis = (scores: any) => {
return (
<div className="space-y-4">
{Object.entries(scores).map(([key, value]) => (
<div key={key} className="p-3 bg-muted/50 rounded-lg">
<div className="font-medium text-sm mb-2">
{key.replace(/_/g, " ")}
</div>
<pre className="text-xs text-muted-foreground overflow-auto max-h-32">
{JSON.stringify(value, null, 2)}
</pre>
</div>
))}
</div>
);
};
return (
<div className="space-y-6">
{/* Header */}
<Card className="border-l-4 border-l-indigo-500">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Brain className="h-5 w-5 text-indigo-600" />
Causal Analysis Results
</CardTitle>
</CardHeader>
<CardContent>
{/* Overall Summary */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
<div className="text-center p-4 bg-blue-50 border border-blue-200 rounded-lg">
<div className="text-2xl font-bold text-blue-600">
{overallStats.totalMethods}
</div>
<div className="text-sm text-blue-700">Analysis Methods</div>
</div>
<div className="text-center p-4 bg-green-50 border border-green-200 rounded-lg">
<div className="text-2xl font-bold text-green-600">
{overallStats.avgCausalScore.toFixed(3)}
</div>
<div className="text-sm text-green-700">Avg Causal Score</div>
</div>
<div className="text-center p-4 bg-purple-50 border border-purple-200 rounded-lg">
<div className="text-2xl font-bold text-purple-600">
{overallStats.maxCausalScore.toFixed(3)}
</div>
<div className="text-sm text-purple-700">Max Causal Score</div>
</div>
<div className="text-center p-4 bg-orange-50 border border-orange-200 rounded-lg">
<div className="text-2xl font-bold text-orange-600">
{overallStats.activeComponents}
</div>
<div className="text-sm text-orange-700">Active Components</div>
</div>
</div>
{/* Perturbation Set Selector */}
{perturbationSets.length > 1 && (
<div className="mb-6">
<h4 className="text-sm font-semibold mb-3">
Perturbation Sets
{selectedSet && (
<span className="ml-2 text-xs font-normal text-muted-foreground">
(Showing results for:{" "}
{getPerturbationSetDisplayName(selectedSet)})
</span>
)}
</h4>
<div className="flex flex-wrap gap-2">
<Button
variant={selectedSet === null ? "default" : "outline"}
size="sm"
onClick={() => setSelectedSet(null)}
className={selectedSet === null ? "ring-2 ring-blue-500" : ""}
>
All Sets
</Button>
{perturbationSets.map((setId) => (
<Button
key={setId}
variant={selectedSet === setId ? "default" : "outline"}
size="sm"
onClick={() => setSelectedSet(setId)}
className={
selectedSet === setId ? "ring-2 ring-blue-500" : ""
}
title={`${getPerturbationSetDisplayName(setId)} (${setId})`}
>
{getPerturbationSetDisplayName(setId)}
</Button>
))}
</div>
</div>
)}
</CardContent>
</Card>
{/* Method Results */}
<div className="space-y-4">
{Object.keys(resultsByMethod).length === 0 ? (
<Card className="border-l-4 border-l-orange-500">
<CardContent className="py-8">
<div className="text-center text-muted-foreground">
<AlertTriangle className="h-12 w-12 mx-auto mb-4 opacity-50" />
<h3 className="text-lg font-medium mb-2">
No Results for Selected Set
</h3>
<p>
{selectedSet
? `No causal analysis results found for "${getPerturbationSetDisplayName(
selectedSet
)}". Try selecting a different set or view all sets.`
: "No causal analysis results are available."}
</p>
</div>
</CardContent>
</Card>
) : (
Object.entries(resultsByMethod).map(([method, results]) =>
renderMethodSummary(method, results)
)
)}
</div>
</div>
);
};
export default CausalResults;