Stage 05 · SpecialtiesModule 21 of 26~6h

Business Intelligence

Dashboards, reports, and decisions powered by Claude.

← All modules in this stage

Most BI work is two things: getting the right numbers, and telling someone what they mean. Claude is unhelpful at the first and surprisingly good at the second. This module shows you how to draw the line cleanly.

By the end of this module you'll have

Time: about 1.5 hours for the basics, ~6 hours with all three notebooks.

Prerequisites: Modules 11 (data analysis), 13 (content creation), 14 (production patterns). Module 9 (RAG) helps for company-context grounding.


The cardinal rule (again)

Compute first, narrate second. The same rule from Module 11. In BI it matters even more, because the numbers usually go in front of an executive who will quote them in a board meeting.

Never paste a CSV into a prompt and ask "what's interesting?". Always:

  1. Run the query (or pandas pipeline) yourself.
  2. Verify the numbers.
  3. Pass the summary (not the raw rows) to Claude for narrative.

Recipe 1 · Weekly digest

Save as weekly_digest.py. Replace the fake numbers with your real query result.

from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv()
client = Anthropic()

# Numbers come from your warehouse, not from Claude.
metrics = {
    "MRR":        {"this_week": 124_500, "last_week": 121_200, "wow_pct": +2.7},
    "Signups":    {"this_week":     412, "last_week":     445, "wow_pct": -7.4},
    "Churn $":    {"this_week":   3_100, "last_week":   2_400, "wow_pct": +29.2},
    "NPS sample": {"this_week":      42, "last_week":      39, "wow_pct": +7.7},
}

context = (
    "Audience: founder + 4-person leadership team.\n"
    "Tone: factual, no marketing words, no hedging. Bullet points OK.\n"
    "Goal: 4–6 sentence digest plus one 'what to watch' line.\n"
    "Rule: do NOT invent numbers; only use the metrics provided."
)

response = client.messages.create(
    model="claude-sonnet-4-6", max_tokens=400,
    system=context,
    messages=[{
        "role": "user",
        "content": f"Write this week's digest from these metrics:\n{metrics}",
    }],
)
print(response.content[0].text)

What you should see: a digest that uses exactly the numbers you gave it, with a line like "Churn dollars rose 29% week-on-week — worth checking which accounts cancelled." That's the value: pattern recognition + clear writing on top of your trusted data.


Recipe 2 · Anomaly alert

When a metric crosses a threshold, send a structured alert that explains the thing in plain language.

def anomaly_alert(metric_name: str, current: float, baseline: float, history: list[float]) -> str:
    pct = (current - baseline) / baseline * 100
    return client.messages.create(
        model="claude-haiku-4-5-20251001",   # alerts can be cheap
        max_tokens=200,
        system=(
            "You are a data analyst writing a one-paragraph anomaly alert. "
            "State the metric, the baseline, the current value, the % change, and "
            "the most likely benign explanation AND a less benign one. Do not invent numbers."
        ),
        messages=[{
            "role": "user",
            "content": (
                f"Metric: {metric_name}\n"
                f"Baseline: {baseline}\nCurrent: {current}\nChange: {pct:+.1f}%\n"
                f"Last 7 values: {history}"
            ),
        }],
    ).content[0].text

print(anomaly_alert("Refund rate", current=0.046, baseline=0.024, history=[0.022, 0.025, 0.026, 0.023, 0.024, 0.027, 0.046]))

The trick is "benign explanation AND less benign". Forces the alert to surface both — readers don't have to read between the lines.


Recipe 3 · Dashboard captions

Every chart needs a caption that tells the reader what they're looking at and what to take away. Generate one from the chart's underlying data:

caption = client.messages.create(
    model="claude-sonnet-4-6", max_tokens=120,
    system=(
        "Write a one-sentence chart caption for an executive dashboard. "
        "Format: '<observation>; <action or watch-out>'. "
        "No marketing words. Use exact numbers."
    ),
    messages=[{
        "role": "user",
        "content": "Chart: Weekly active users (last 8 weeks): 1.8k, 2.0k, 2.2k, 2.1k, 2.3k, 2.4k, 2.6k, 2.9k.",
    }],
).content[0].text

print(caption)

Wire this into your BI tool's chart description field; you've just upgraded every chart in the org.


Things to watch for

Risk Mitigation
Numbers in the narrative don't match the chart Pass numbers as data, never as prose. Lock them with a JSON wrapper.
Claude invents context ("usually we see…") Forbid it in the system prompt. "Use only the metrics provided."
Tone drifts toward marketing Anti-words list. "Do not use 'leverage', 'robust', 'seamless'."
Same digest every week Inject "what changed this week vs the prior digest" as context.
Sensitive data leaks to logs Strip PII, customer names before sending; same applies to logs (Module 14).

Try changing one thing


Going deeper: open the notebooks


Module checklist


Next module

Module S2 · Research & Academia — applying the same discipline to literature review and writing.