Business Intelligence
Dashboards, reports, and decisions powered by Claude.
← All modules in this stageMost 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
- A working pattern: SQL/pandas computes, Claude narrates
- A reliable executive summary generator from a query result
- Templates for the three common BI artefacts: weekly digest, anomaly alert, dashboard caption
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:
- Run the query (or pandas pipeline) yourself.
- Verify the numbers.
- 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
- Replace the digest's audience: write it for the engineering team instead. Watch the tone shift on the same numbers.
- Add a "things we said last week" section by feeding last week's digest in as context.
- Build a tool-using version (Module 8): Claude can call
top_n_by(metric, n)andchange(metric, period)itself instead of getting metrics handed over. - Add a verifier (Module 15): a second prompt that re-checks every number in the digest against the source data.
Going deeper: open the notebooks
notebooks/01_introduction.ipynb— digests, captions, anomaly framing (~1.5–2h)notebooks/02_intermediate.ipynb— natural-language interfaces over real data (~2–3h)notebooks/03_advanced.ipynb— governance, ownership, embedding into BI tools (~1.5–2.5h)
Module checklist
- [ ] You've generated a digest from real numbers and confirmed nothing was invented
- [ ] You've added an anti-marketing-words rule to a system prompt and seen tone improve
- [ ] You can describe the line between "what your warehouse computes" and "what Claude narrates"
Next module
Module S2 · Research & Academia — applying the same discipline to literature review and writing.