---
title: "Template: Data Enrichment"
description: "LLM-powered enrichment of existing database records -- read rows, enrich each with an LLM, write results back to Supabase"
loadWhen: "Applying the data-enrichment workflow template"
---

**Category:** Data Processing

**Platform Tools:** `llm` (text generation), `supabase` (select and update)

**Credentials Required:**

- `my-database` -- Supabase project URL and service role key
- LLM API keys are resolved server-side from platform configuration (no credential name needed)

---

## What This Workflow Does

Fetches records from a Supabase table that need enrichment, sends each record to an LLM with a custom prompt, and writes the enriched field back to the table. Supports batching to process multiple records per execution. Suitable for enriching leads with AI-generated summaries, classifying records, extracting structured data from text fields, or scoring records.

---

## Input Schema

```typescript
z.object({
  table: z.string(),              // Supabase table to enrich
  filter: z.record(z.string()).optional(), // Filter rows to enrich (PostgREST format)
  sourceField: z.string(),        // Field to send to the LLM as input
  targetField: z.string(),        // Field to write enriched output to
  prompt: z.string(),             // LLM prompt template (use {value} as placeholder)
  limit: z.number().optional(),   // Max rows to process (default: 20)
})
```

## Output Schema

```typescript
z.object({
  processed: z.number(),          // Rows successfully enriched
  skipped: z.number(),            // Rows skipped (missing source field, LLM error)
  errors: z.array(z.string()),    // Error messages for skipped rows
})
```

---

## Workflow Code Pattern

```typescript
import type { WorkflowDefinition } from '@elevasis/sdk'
import { platform } from '@elevasis/sdk/worker'
import { z } from 'zod'

const inputSchema = z.object({
  table: z.string(),
  filter: z.record(z.string()).optional(),
  sourceField: z.string(),
  targetField: z.string(),
  prompt: z.string(),
  limit: z.number().optional(),
})
const outputSchema = z.object({
  processed: z.number(),
  skipped: z.number(),
  errors: z.array(z.string()),
})

type Input = z.infer<typeof inputSchema>

export const dataEnrichment: WorkflowDefinition = {
  config: {
    resourceId: 'data-enrichment',
    name: 'Data Enrichment',
    type: 'workflow',
    description: 'Enriches database records using an LLM',
    version: '1.0.0',
    status: 'dev',
  },
  contract: { inputSchema, outputSchema },
  steps: {
    enrich: {
      id: 'enrich',
      name: 'Enrich Records',
      description: 'Fetch records, call LLM for each, write results back',
      inputSchema,
      outputSchema,
      handler: async (input, context) => {
        const { table, filter, sourceField, targetField, prompt, limit } = input as Input

        // Fetch rows to enrich
        const rows = await platform.call({
          tool: 'supabase',
          method: 'select',
          credential: 'my-database',
          params: { table, filter, limit: limit ?? 20 },
        }) as Array<Record<string, unknown>>

        let processed = 0
        const errors: string[] = []

        for (const row of rows) {
          const sourceValue = row[sourceField]
          if (!sourceValue || typeof sourceValue !== 'string') {
            errors.push(`Row ${String(row.id)}: missing ${sourceField}`)
            continue
          }

          try {
            const enrichedPrompt = prompt.replace('{value}', sourceValue)
            const result = await platform.call({
              tool: 'llm',
              method: 'generate',
              params: {
                provider: 'openai',
                model: 'gpt-5.4-mini',
                messages: [{ role: 'user', content: enrichedPrompt }],
              },
            }) as string

            await platform.call({
              tool: 'supabase',
              method: 'update',
              credential: 'my-database',
              params: {
                table,
                filter: { id: `eq.${String(row.id)}` },
                data: { [targetField]: result },
              },
            })

            context.logger.info('Enriched row', { id: row.id })
            processed++
          } catch (err) {
            const msg = err instanceof Error ? err.message : String(err)
            errors.push(`Row ${String(row.id)}: ${msg}`)
          }
        }

        return { processed, skipped: errors.length, errors }
      },
      next: null,
    },
  },
  entryPoint: 'enrich',
}
```

---

## Adaptation Notes

- **Credential name:** Replace `'my-database'` with the user's database credential name.
- **LLM provider and model:** Default uses `openai` / `gpt-5.4-mini`. Adapt to the user's preference or available providers.
- **Prompt template:** The `{value}` placeholder is replaced with the source field's value at runtime. Adapt the prompt for the user's specific enrichment task.
- **Batch size:** Default limit is 20 rows. Increase for bulk processing, decrease for expensive LLM calls.
- **Skill adaptation:** For beginners, explain what "enrichment" means and give concrete examples (adding a summary field, scoring, categorizing) before generating code.

---

**Last Updated:** 2026-02-26
