← Back to blog
ai·May 13, 2026·7 min read

Building an AI financial analyst with Claude and dbt

We built AI agents on top of 50+ QuickBooks P&L pipelines. Here's what a financial analyst agent actually looks like — from warehouse to variance analysis to Slack.

ai

Building an AI financial analyst with Claude and dbt

A CFO opens Slack and types: "Why is net income down 12% this month?"

Thirty seconds later, Claude responds with the top 5 variance drivers sorted by magnitude, a comparison to the same month last year, and a flag that payroll increased 18% because of the new hire batch in engineering.

No dashboard. No analyst queue. No waiting until the monthly close meeting to learn that the numbers moved.

We've built this. Not as a demo — as a production system sitting on top of real financial data from 50+ QuickBooks clients. Here's how it works.

The architecture

QuickBooks ──→ Fivetran ──→ BigQuery (raw)
                                │
                                ▼
                          dbt models
                    (staging → mart P&L)
                                │
                                ▼
                     Claude API + MCP
                    (BigQuery MCP server)
                                │
                                ▼
                    Slack bot / CLI / Web UI

Three layers, each doing one job:

  1. Data layer — Fivetran syncs QuickBooks to BigQuery. dbt transforms raw transactions into a tested, documented P&L mart.
  2. Intelligence layer — Claude connects to BigQuery via MCP, reads the dbt-modeled data, and answers questions in natural language.
  3. Interface layer — Slack bot, CLI, or web UI. The CFO asks questions where they already work.

The data foundation (non-negotiable)

The AI agent is only as good as the data it reads. If your P&L model is wrong, Claude will confidently explain wrong numbers. The dbt layer is where accuracy lives.

From our QuickBooks post, the key models:

-- models/marts/mart_profit_and_loss.sql
SELECT
    DATE_TRUNC(t.transaction_date, MONTH) AS month,
    a.classification,       -- Revenue, Expense, etc.
    a.account_type,         -- Operating, COGS, etc.
    a.account_name,
    SUM(CASE
        WHEN a.classification = 'Revenue' THEN -t.line_amount
        WHEN a.classification = 'Expense' THEN t.line_amount
        ELSE 0
    END) AS net_impact
FROM {{ ref('int_quickbooks__all_transactions') }} t
JOIN {{ ref('stg_quickbooks__accounts') }} a
    ON t.account_id = a.account_id
GROUP BY 1, 2, 3, 4
-- models/marts/mart_monthly_summary.sql
SELECT
    month,
    SUM(CASE WHEN classification = 'Revenue' THEN net_impact END) AS total_revenue,
    SUM(CASE WHEN classification = 'Expense' AND account_type = 'CostOfGoodsSold'
        THEN net_impact END) AS cogs,
    SUM(CASE WHEN classification = 'Expense' AND account_type != 'CostOfGoodsSold'
        THEN net_impact END) AS operating_expenses,
    SUM(net_impact) AS net_income
FROM {{ ref('mart_profit_and_loss') }}
GROUP BY 1

These models are tested:

models:
  - name: mart_monthly_summary
    tests:
      - dbt_utils.expression_is_true:
          expression: "total_revenue >= 0"
      - dbt_utils.expression_is_true:
          expression: "net_income = total_revenue - cogs - operating_expenses"

When Claude queries this data, it's querying numbers that have passed automated checks. That's the difference between a demo and a production system.

The agent: what it can do

Variance analysis

The highest-value capability. The agent answers "why did X change?" by decomposing the change into contributing accounts:

User: Why is net income down this month?

Claude's process:

  1. Query mart_monthly_summary for current and prior month
  2. Calculate the delta
  3. Query mart_profit_and_loss for account-level changes
  4. Sort by absolute variance
  5. Format and respond

Response:

Net income decreased $23,400 (12.1%) month-over-month.

Top drivers:

  1. Payroll — up $18,200 (18.3%). Three new engineering hires started mid-month.
  2. Software subscriptions — up $4,100 (22%). Annual Snowflake contract renewed at higher tier.
  3. Revenue — down $2,800 (1.4%). Seasonal dip consistent with prior year.
  4. Travel — up $1,900. Conference expenses (one-time).
  5. Office supplies — down $3,600. Last month had furniture purchase.

The agent doesn't just report the number — it explains the composition.

Trend detection

User: Any unusual expenses in the last 3 months?

Claude queries the rolling average and standard deviation per account, flags anything beyond 2 standard deviations:

Two anomalies in the last 90 days:

  1. Legal fees — $12,400 in March vs. $1,200 average. 10.3× normal. Likely one-time.
  2. Contractor payments — $28,000 in February vs. $8,500 average. 3.3× normal. Coincides with the data-migration project.

Scenario planning

User: What happens to net income if we increase payroll 15% next quarter?

Claude queries the current payroll run rate, applies the increase, and projects:

Current quarterly payroll: $298,000 With 15% increase: $342,700 Additional cost: $44,700/quarter ($14,900/month)

Projected impact on net income:

  • Current quarterly net income: $89,200
  • Projected: $44,500 (50.1% decrease)

Note: this assumes revenue holds flat. If Q2 follows the seasonal pattern from last year, revenue typically increases 8-12% vs Q1, which would partially offset.

Period comparison

User: Compare Q1 this year to Q1 last year.

Claude queries both periods, calculates deltas, and highlights the biggest shifts. Year-over-year context is automatic because the data is in the warehouse going back to the first Fivetran sync.

Building the Slack interface

The simplest production interface: a Slack bot that forwards messages to the Claude API with the MCP connection to BigQuery.

# Simplified Slack bot handler
import anthropic
from slack_bolt import App
 
client = anthropic.Anthropic()
app = App(token=os.environ["SLACK_BOT_TOKEN"])
 
SYSTEM_PROMPT = """
You are a financial analyst for {company_name}. You have access to BigQuery
via MCP, which contains a dbt-modeled P&L from QuickBooks data.
 
Key tables:
- mart_profit_and_loss: monthly account-level P&L
- mart_monthly_summary: monthly revenue, COGS, opex, net income
- stg_quickbooks__accounts: chart of accounts with classifications
 
When answering questions:
1. Always query the actual data — never guess or use cached numbers.
2. Show the key numbers, then explain what drove the change.
3. Flag anything unusual (>2 standard deviations from rolling average).
4. If the user asks about payroll, note that QuickBooks API doesn't expose
   payroll detail — the numbers come from journal entry backfill.
"""
 
@app.message("")
def handle_message(message, say):
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        system=SYSTEM_PROMPT,
        messages=[{"role": "user", "content": message["text"]}],
        # MCP tools configured in the client
    )
    say(response.content[0].text)

The system prompt is where domain expertise lives. It tells Claude about the data model, the known limitations (payroll gap), and how to structure responses. This prompt is the difference between a generic SQL chatbot and an actual financial analyst.

What this replaces (and what it doesn't)

It replaces:

  • Ad-hoc analyst requests. "Can you pull the numbers for..." queries that take 30 minutes to answer manually.
  • Monthly variance explanations. The "why did this change?" conversation that happens in every finance meeting.
  • Dashboard navigation. A CFO asking a question in Slack is faster than logging into Looker, finding the right dashboard, setting the right filters, and interpreting the chart.

It doesn't replace:

  • Judgment calls. Claude can tell you payroll increased 18%. It can't tell you whether the new hires were the right decision.
  • Forecasting with nuance. Simple projections work (increase X by 15%). Complex scenario models with interdependencies need a proper FP&A tool or spreadsheet.
  • Audit-grade accuracy. The numbers come from dbt-tested models, but the natural-language explanations are Claude's interpretation. For board-level reporting, verify the key numbers in the source system.

The cost

ComponentMonthly cost
Fivetran (QuickBooks connector)$0-100
BigQuery (storage + compute)$20-50
dbt Cloud (transformation)$0-100
Claude API (queries)$10-50
Slack (bot)Free with existing workspace
Total$30-300/month

Compare that to a fractional CFO ($3-8K/month) or a dedicated analyst ($5-10K/month). The AI agent doesn't replace either — but it handles the 80% of questions that are "look up a number and explain the context," freeing the humans for the 20% that requires judgment.

Why dbt is the key ingredient

You could connect Claude directly to raw QuickBooks tables in BigQuery. Don't.

Raw tables have cryptic column names, denormalized schemas, and no documentation. Claude would spend half its token budget figuring out what TxnDate means and whether Amount is in cents or dollars.

dbt models give you:

  • Clean column namestransaction_date, line_amount (in dollars)
  • Documentation — each column has a description Claude can read
  • Tests — the agent queries data that has passed automated quality checks
  • Lineage — Claude can trace which raw tables feed which marts

The dbt documentation is the semantic layer. No separate YAML file to maintain. No vendor-specific semantic model. Just well-documented SQL models that serve both human analysts and AI agents.


We've built financial AI agents on top of QuickBooks, Xero, and NetSuite data — from Fivetran extraction to dbt models to Claude-powered Slack bots. If your finance team is still waiting on analyst queues for basic questions, book a discovery call.

Got a similar problem?

30 minutes. We'll tell you honestlywhat's broken.