# Report Builder

> Build self-contained, single-page HTML reports and dashboards from data using Python + Jinja2 + Bootstrap 5 with Chart.js or Plotly. Use whenever the task is to produce an HTML report, data dashboard, metrics page, status page, or any single-file web artifact rendered from data — even if the user only says "report", "dashboard", "summary page", "visualize this", or hands you a CSV/DataFrame/JSON…

- **Type:** Skill
- **Install:** `agentstack add skill-kevin-burns-claude-skills-report-builder`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [kevin-burns](https://agentstack.voostack.com/s/kevin-burns)
- **Installs:** 0
- **Category:** [Data & Analytics](https://agentstack.voostack.com/c/data-and-analytics)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [kevin-burns](https://github.com/kevin-burns)
- **Source:** https://github.com/kevin-burns/claude-skills/tree/main/report-builder
- **Website:** https://techblog.kevinburns.de

## Install

```sh
agentstack add skill-kevin-burns-claude-skills-report-builder
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Report Builder

The job is almost always the same shape: **data in → one HTML file out**, rendered by a
template, viewable by double-clicking with no server. Keep that shape and the work stays
small and reproducible. The two failure modes to design against are (1) non-determinism
(charts that fetch live, timestamps that drift) and (2) unsafe data interpolation (raw
values dropped into HTML or ``). Both are solved below.

## The pattern

```
data (CSV / DataFrame / JSON)
   └─ your prep code (pandas, optional)  →  a plain JSON "context"
        └─ Jinja2 template (.html.j2)    →  scripts/render.py
             └─ one self-contained report.html
```

Separate **prep** (project-specific, may use pandas) from **render** (generic, reusable).
`scripts/render.py` is the generic half — point it at a template and a JSON context file
and it emits HTML. Don't rewrite it per project.

Run `render.py` by its **absolute path** — it lives in this skill's base directory (announced
when the skill loads, usually `~/.claude/skills/report-builder`); a relative
`report-builder/scripts/render.py` won't resolve from another repo. It has PEP 723 inline deps
(jinja2), so it **must** run under `uv` — never bare `python`. Define an `rbuild` function per
block (a function passes arguments correctly under bash and zsh, where a plain `$VAR` doesn't
word-split; re-declare it per block since shell state doesn't persist between tool calls):

```bash
# uv is required here (jinja2); resolve it even when it's not on a bare PATH (non-interactive
# shells often drop ~/.local/bin or the Homebrew bin -> `uv: command not found`):
UV="$(command -v uv || ls "$HOME/.local/bin/uv" "$HOME/.cargo/bin/uv" /opt/homebrew/bin/uv /usr/local/bin/uv 2>/dev/null | head -1)"
rbuild() { "$UV" run "$HOME/.claude/skills/report-builder/scripts/render.py" "$@"; }

rbuild --template my_report.html.j2 \
  --data context.json \
  --out report.html --title "Q2 Cost Report"
```

## Two rules that prevent the common bugs

1. **Autoescape stays ON.** `render.py` enables it and uses `StrictUndefined`, so a typo'd
   variable fails loudly instead of rendering blank, and text values can't inject markup.
   Only reach for `| safe` on content **you** generated and trust (e.g. a Markdown→HTML
   block you produced). Never `| safe` on data that originated from a user, a file, or an
   API.
2. **Pass data to JavaScript with `| tojson`, never string-building.** To hand a dataset to
   Chart.js/Plotly, serialize it in the template:
   ```html
   
     const chartData = {{ data | tojson }};   {# safe: escapes , quotes, unicode #}
   
   ```
   This is correct *and* XSS-safe. Building JS by concatenating values is the classic
   injection hole — don't.

## Chart.js or Plotly — pick per report

| Use **Chart.js** when | Use **Plotly** when |
|---|---|
| Standard charts: bar, line, pie, doughnut, radar | Interactive exploration: zoom, pan, rich hover, selection |
| Small bundle, fast, dashboard-y look | Statistical/scientific: box, violin, heatmap, 3D, contour |
| Few hundred → few thousand points | Large datasets, or users will drill into the data |
| You want a clean default aesthetic with little config | You need subplots, dual axes, or precise scientific layout |

Default to **Chart.js** for status/metrics dashboards; reach for **Plotly** when the reader
needs to *interrogate* the data, not just read it. Don't load both unless a report truly
needs each.

## Self-contained vs. CDN

- **CDN** (default for internal/online reports): Bootstrap 5, Chart.js, Plotly via
  ``. Smallest file, but needs network at view time.
- **Vendored / inline** (for portable, air-gapped, or archived reports): download the libs
  and inline them, or commit them next to the report. Choose this when the report must open
  with no network, or must look identical years later. State which mode you chose in the
  report's provenance footer.

The starter template (`assets/report-template.html.j2`) uses CDN with the swap point
marked. Pin a major version in the URL; **verify the current version** rather than guessing
a patch number (see Fact-discipline).

## Layout with Bootstrap 5

Lean on the grid and components instead of hand-rolling CSS:
- Page frame: a `container`/`container-fluid`, a header row, then `row`/`col-*` for content.
- KPIs: `card` components in a responsive `row-cols-1 row-cols-md-3` grid.
- Tables: ``; for long tables, wrap in
  `table-responsive`.
- Keep custom CSS to a small `` block for things Bootstrap doesn't cover (chart
  container heights, print tweaks). Don't fight the framework.

## Reproducibility (so the same data gives the same report)

- Pass any "generated at" timestamp **in** via the context (`--data`), don't call
  `datetime.now()` inside the template — that way a re-render of the same inputs is
  byte-stable, and tests can assert on output.
- Record provenance in a footer: source of the data, the render command, lib versions/mode
  (CDN vs vendored). A report a reader can't trace is a report they can't trust.

## Fact-discipline (library versions & APIs)

Don't assert a current Bootstrap/Chart.js/Plotly version or a chart-config field from
memory — these drift. Confirm the version and the option name before pinning:

```bash
# resolve then fetch — see the c7search skill (avoid `ask`)
LIB=$(c7search resolve --library-name "chart.js" "bar chart options" --json --limit 1 | jq -r '.[0].id')
c7search docs "$LIB" --topic "bar chart options" --tokens 3000
```

Or check the official docs/CDN. If you can't verify, leave the version as a clearly-marked
placeholder rather than inventing a number. (Defer to the `fact-verifier` agent when a
version gates the build.)

## Files in this skill

- `scripts/render.py` — generic Jinja2 renderer (autoescape + StrictUndefined; PEP 723
  inline deps; `uv run`-ready). Reuse it; don't reinvent it.
- `assets/report-template.html.j2` — starter: Bootstrap 5 frame, KPI cards, a Chart.js
  example wired through `| tojson`, a data table, and a provenance footer. Copy and adapt.

## Quick start

1. Copy `assets/report-template.html.j2` into the project; adapt the markup.
2. Write a small prep step that produces a JSON context (`{"title": ..., "kpis": [...],
   "chart": {...}, "rows": [...], "generated_at": "...", "provenance": {...}}`).
3. `rbuild --template  --data  --out report.html` (define `rbuild` as shown above).
4. Open `report.html`. Verify chart renders, table populates, no `Undefined` errors.

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [kevin-burns](https://github.com/kevin-burns)
- **Source:** [kevin-burns/claude-skills](https://github.com/kevin-burns/claude-skills)
- **License:** MIT
- **Homepage:** https://techblog.kevinburns.de

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-kevin-burns-claude-skills-report-builder
- Seller: https://agentstack.voostack.com/s/kevin-burns
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
