← Back to cookbook

CRM Enrichment

Finds Attio companies missing a description, researches each on the web, and writes the findings back onto the record.

integrationmapsearchai

Source

/**
 * CRM enrichment. Once a week it walks the companies in Attio, finds the
 * ones missing a description, researches each on the web, and writes the
 * description and industry back onto the record.
 *
 * Setup:
 *   1. swirls add attio query_records update_record
 *   2. Deploy, then authorize the Attio connection in Cloud -> Connections.
 */

connection team_attio {
  label: "Attio CRM"
  provider: attio
}

schedule weekly_enrichment {
  label: "Weekly Enrichment"
  cron: "0 6 * * 1"
}

workflow enrich_companies {
  label: "Enrich Companies"
  description: "Research companies missing a description in Attio and fill in description and industry."

  root {
    type: code
    label: "Start"
    code: @ts { return { batchSize: 25 } }
    outputSchema: @json {
      {
        "type": "object",
        "properties": { "batchSize": { "type": "number" } }
      }
    }
  }

  node fetch_companies {
    type: integration
    label: "Fetch companies"
    connection: team_attio
    action: attio_query_records
    params: @ts {
      return {
        object: "companies",
        limit: context.nodes.root.output.batchSize
      }
    }
  }

  node enrich_each {
    type: map
    label: "Enrich each company"
    items: @ts {
      const rows = context.nodes.fetch_companies.output.data || []
      return rows.map(r => {
        const values = r.values || {}
        const nameEntry = (values.name && values.name[0]) || {}
        const domainEntry = (values.domains && values.domains[0]) || {}
        const descEntry = (values.description && values.description[0]) || {}
        return {
          record_id: r.id ? r.id.record_id : "",
          name: nameEntry.value || "",
          domain: domainEntry.domain || "",
          description: descEntry.value || ""
        }
      }).filter(c => c.record_id && c.name && !c.description)
    }
    maxItems: 25

    subgraph {
      root {
        type: code
        label: "Company"
        inputSchema: @json {
          {
            "type": "object",
            "properties": {
              "record_id": { "type": "string" },
              "name": { "type": "string" },
              "domain": { "type": "string" }
            }
          }
        }
        code: @ts { return context.iteration.item }
        outputSchema: @json {
          {
            "type": "object",
            "properties": {
              "record_id": { "type": "string" },
              "name": { "type": "string" },
              "domain": { "type": "string" }
            }
          }
        }
      }

      node research {
        type: search
        label: "Research company"
        query: @ts {
          const c = context.nodes.root.output
          return c.name + " " + c.domain + " company overview"
        }
        limit: 5
      }

      node summarize {
        type: ai
        label: "Summarize"
        kind: object
        model: "google/gemini-2.5-flash"
        prompt: @ts {
          const c = context.nodes.root.output
          const results = (context.nodes.research.output.results || [])
            .map(r => "- " + (r.title || "") + ": " + (r.description || ""))
            .join("\n")
          return "Write a two-sentence factual description of the company " + c.name + " (" + c.domain + ") and name its industry in a few words. Only state what the search results support.\n\nSearch results:\n" + results
        }
        schema: @json {
          {
            "type": "object",
            "required": ["description", "industry"],
            "properties": {
              "description": { "type": "string" },
              "industry": { "type": "string" }
            }
          }
        }
      }

      node update_company {
        type: integration
        label: "Update record"
        connection: team_attio
        action: attio_update_record
        params: @ts {
          const c = context.nodes.root.output
          const s = context.nodes.summarize.output
          return {
            object: "companies",
            record_id: c.record_id,
            data: {
              values: {
                description: s.description + " Industry: " + s.industry
              }
            }
          }
        }
      }

      node done {
        type: code
        label: "Record result"
        code: @ts {
          return { name: context.nodes.root.output.name, enriched: true }
        }
      }

      flow {
        root -> research
        research -> summarize
        summarize -> update_company
        update_company -> done
      }
    }
  }

  node summarize_run {
    type: code
    label: "Summarize run"
    code: @ts {
      const rows = (context.nodes.enrich_each.output || []).map(r => r.done).filter(Boolean)
      return { enriched: rows.length, companies: rows.map(r => r.name) }
    }
  }

  flow {
    root -> fetch_companies
    fetch_companies -> enrich_each
    enrich_each -> summarize_run
  }
}

trigger on_weekly_enrichment {
  schedule:weekly_enrichment -> enrich_companies
  enabled: true
}

Flow

Trigger to workflow

Workflow nodes