Skip to main content

Overview

Meridian’s Insights Discovery feature uses AI to automatically analyze your data and surface meaningful patterns, trends, anomalies, and statistical findings. The insights panel provides actionable intelligence without requiring manual analysis.

Insight Types

Insights are categorized into five types:

Outliers

Data points that deviate significantly from expected patterns

Trends

Directional movements and patterns over time

Aggregations

Summary statistics and calculations across groups

Patterns

Recurring behaviors and relationships in the data

Anomalies

Unusual occurrences that warrant attention

Insight Structure

interface Insight {
  title: string
  description: string
  type: 'outlier' | 'trend' | 'aggregation' | 'pattern' | 'anomaly'
  severity: 'low' | 'medium' | 'high'
}

Severity Levels

  • High: Requires immediate attention, significant impact
  • Medium: Notable finding, should be reviewed
  • Low: Informational, good to know

Insights Panel

The insights panel is implemented in InsightsPanel.tsx:
import { InsightsPanel } from '@/components/InsightsPanel'

<InsightsPanel
  insights={insights}
  isLoading={isGenerating}
  onGenerate={handleGenerate}
  onRefresh={handleRefresh}
  onDismiss={handleDismiss}
  error={error}
  hasData={hasData}
/>

Panel Features

1

Collapsible Header

Expand/collapse to save screen space:
<ActionIcon onClick={() => setExpanded(v => !v)}>
  {expanded ? <IconChevronUp /> : <IconChevronDown />}
</ActionIcon>
2

Insight Count Badge

Shows total number of insights:
{insights.length > 0 && (
  <Badge size="xs" variant="light" color="violet">
    {insights.length}
  </Badge>
)}
3

Refresh Control

Regenerate insights (bypasses cache):
<ActionIcon onClick={onRefresh} loading={isLoading}>
  <IconRefresh size={16} />
</ActionIcon>
4

Scrollable Content

Insights list with smooth scrolling:
<ScrollArea
  maxHeight="calc(100vh - 200px)"
  type="auto"
  offsetScrollbars
>
  {insights.map((insight, idx) => (
    <InsightCard key={idx} insight={insight} />
  ))}
</ScrollArea>

Insight Cards

Each insight is displayed in an interactive card:
function InsightCard({ insight }: { insight: Insight }) {
  const [expanded, setExpanded] = useState(false)
  
  return (
    <Box
      onClick={() => setExpanded(v => !v)}
      style={{
        backgroundColor: 'rgba(255,255,255,0.87)',
        borderRadius: 'var(--mantine-radius-md)',
        border: `1px solid ${getSeverityColor(insight.severity)}`,
        cursor: 'pointer',
      }}
    >
      <Group justify="space-between">
        <Group gap="xs">
          {getInsightIcon(insight.type)}
          <Text fw={600}>{insight.title}</Text>
        </Group>
        <Badge 
          size="xs" 
          color={getSeverityColor(insight.severity)}
        >
          {insight.severity}
        </Badge>
      </Group>
      
      <Text 
        size="xs" 
        c="dimmed"
        style={{
          overflow: expanded ? undefined : 'hidden',
          textOverflow: expanded ? 'unset' : 'ellipsis',
          whiteSpace: expanded ? 'pre-wrap' : 'nowrap',
        }}
      >
        {insight.description}
      </Text>
    </Box>
  )
}
From /home/daytona/workspace/source/src/components/InsightsPanel.tsx:69

Generating Insights

Manual Generation

const handleGenerateInsights = async () => {
  setIsGenerating(true)
  setError(null)
  
  try {
    const result = await generateInsights({
      tableName: currentTable,
      data: tableData,
    })
    
    setInsights(result.insights)
  } catch (err) {
    setError(err.message)
  } finally {
    setIsGenerating(false)
  }
}

Automatic Generation

Insights can be generated automatically:
  • After query execution
  • When new data is uploaded
  • On table change
  • Via AI agent

Cache Strategy

Insights are cached to avoid redundant processing:
// Check cache first
const cached = await getCachedInsights({ tableName })
if (cached && !forceRefresh) {
  return cached.insights
}

// Generate new insights
const newInsights = await generateWithAI({ tableName, data })

// Cache results
await cacheInsights({
  tableName,
  insights: newInsights,
  expiresAt: Date.now() + 3600000, // 1 hour
})
See Insights Cache API for details.

Icon & Color Mapping

Type Icons

function getInsightIcon(type: Insight['type']) {
  switch (type) {
    case 'outlier':
    case 'anomaly':
      return <IconAlertTriangle size={16} />
    case 'trend':
      return <IconTrendingUp size={16} />
    case 'aggregation':
      return <IconChartBar size={16} />
    default:
      return <IconBrain size={16} />
  }
}

Severity Colors

function getSeverityColor(severity: Insight['severity']) {
  switch (severity) {
    case 'high':
      return 'red'
    case 'medium':
      return 'orange'
    case 'low':
      return 'blue'
  }
}

Statistical Findings

The StatisticalFindingsPanel provides detailed statistical analysis:
import { StatisticalFindingsPanel } from '@/components/StatisticalFindingsPanel'

<StatisticalFindingsPanel findings={findings} />

Finding Structure

type StatisticalFinding = {
  columnName: string
  columnType: string
  hasNumericData: boolean
  stats?: {
    mean: number
    median: number
    stdDev: number
    min: number
    max: number
    q1: number
    q3: number
    iqr: number
    count: number
  }
}

Statistical Metrics

Sum of all values divided by count:
const mean = data.reduce((sum, val) => sum + val, 0) / data.length
Middle value when sorted:
const sorted = [...data].sort((a, b) => a - b)
const median = sorted[Math.floor(sorted.length / 2)]
Measure of spread from the mean:
const variance = data.reduce((sum, val) => 
  sum + Math.pow(val - mean, 2), 0) / data.length
const stdDev = Math.sqrt(variance)
25th and 75th percentiles:
const sorted = [...data].sort((a, b) => a - b)
const q1 = sorted[Math.floor(sorted.length * 0.25)]
const q3 = sorted[Math.floor(sorted.length * 0.75)]
const iqr = q3 - q1
Relative variability measure:
const cv = (stdDev / mean) * 100
// < 15%: Low variability (green)
// 15-30%: Moderate (yellow)
// 30-50%: High (orange)
// > 50%: Very high (red)

Visualization Components

Stat Cards

<StatCard
  title="Average Mean"
  value={aggregateStats.avgMean}
  description="Across all columns"
  icon={<IconChartBar size={24} />}
  color="violet"
/>

Ring Progress

Shows coefficient of variation:
<RingProgress
  size={60}
  thickness={6}
  sections={[
    {
      value: Math.min(cv, 100),
      color: getVariabilityColor(cv),
    },
  ]}
  label={
    <Text size="xs" ta="center" fw={700}>
      {cv.toFixed(0)}%
    </Text>
  }
/>

Distribution Progress

Visualizes mean and median position:
<Progress
  value={meanPercent}
  color="blue"
  size="sm"
  radius="xl"
/>
<Progress
  value={medianPercent}
  color="violet"
  size="sm"
  radius="xl"
/>
From /home/daytona/workspace/source/src/components/StatisticalFindingsPanel.tsx:559

Empty States

No Data

{!hasData && (
  <Box p="lg" style={{ textAlign: 'center' }}>
    <IconBrain size={32} style={{ color: 'var(--mantine-color-gray-4)' }} />
    <Text size="sm" c="dimmed">
      Run a query to generate insights
    </Text>
  </Box>
)}

No Insights Yet

{hasData && insights.length === 0 && (
  <Box p="lg" style={{ textAlign: 'center' }}>
    <Text size="sm" c="dimmed" mb="md">
      No insights generated yet
    </Text>
    <Button
      leftSection={<IconSparkles size={16} />}
      onClick={onGenerate}
      loading={isLoading}
      variant="light"
      color="violet"
    >
      Generate Insights
    </Button>
  </Box>
)}

Integration with AI Agent

The AI agent can generate insights as part of its analysis:
// User: "What insights can you find in this data?"

// Agent uses generateInsights tool
const insights = await generateInsights({
  tableName: 'sales',
  data: queryResults,
})

// Returns structured insights
// {
//   insights: [
//     {
//       title: "Revenue spike in Q4",
//       description: "Revenue increased 45% in Q4 compared to Q3...",
//       type: "trend",
//       severity: "high"
//     },
//     ...
//   ]
// }

Performance Optimization

Lazy Loading

const [insights, setInsights] = useState<Insight[]>([])
const [isLoading, setIsLoading] = useState(false)

useEffect(() => {
  // Only load when panel is expanded
  if (expanded && insights.length === 0) {
    loadInsights()
  }
}, [expanded])

Debounced Generation

const debouncedGenerate = useDebouncedCallback(
  async () => {
    await generateInsights()
  },
  1000 // Wait 1s after last change
)

// Trigger on data changes
useEffect(() => {
  if (hasData) {
    debouncedGenerate()
  }
}, [tableData])

Pagination

const [page, setPage] = useState(1)
const itemsPerPage = 10
const visibleInsights = insights.slice(
  (page - 1) * itemsPerPage,
  page * itemsPerPage
)

Advanced Tips

Define custom rules for your domain:
const customInsights = [
  {
    condition: (data) => data.revenue < 1000,
    insight: {
      title: "Low Revenue Alert",
      description: "Revenue below threshold",
      type: "anomaly",
      severity: "high"
    }
  }
]
Add actions to insights:
<Button
  onClick={() => {
    // Create query based on insight
    setQuery(insight.suggestedQuery)
    // Or create chart
    createChart(insight.chartConfig)
  }}
>
  Investigate
</Button>
Save insights for reporting:
const exportInsights = () => {
  const data = JSON.stringify(insights, null, 2)
  const blob = new Blob([data], { type: 'application/json' })
  downloadBlob(blob, 'insights.json')
}

API Reference

Insights use the following APIs:
  • api.insights.generate - Generate new insights
  • api.insights.get - Retrieve cached insights
  • api.insights.refresh - Force regeneration
See Insights Tool API for full documentation.

Next Steps

AI Agents

Use AI agents to automatically analyze your data

Statistical Analysis

Learn how to interpret statistical findings

Build docs developers (and LLMs) love