# How /html-report Generates Offline SVG Dashboards from Tracker CSV

> Learn how /html-report generates offline SVG dashboards from your tracker CSV. It uses pandas and matplotlib to build summaries and charts integrated into a self-contained HTML file.

- Repository: [Mads Lorentzen/ai-job-search](https://github.com/MadsLorentzen/ai-job-search)
- Tags: how-to-guide
- Published: 2026-07-19

---

**The `/html-report` command reads the job‑application tracker CSV with pandas, builds summary statistics and matplotlib SVG charts, and embeds them into a self‑contained HTML template to create a fully offline dashboard.**

The `MadsLorentzen/ai-job-search` repository provides a CLI tool that converts raw application tracking data into portable, vector‑graphic reports. By combining **pandas** for data manipulation and **matplotlib**’s SVG backend, the utility produces standalone HTML files that render without external dependencies or internet access. This article walks through the exact pipeline implemented in [`tools/html_report.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tools/html_report.py) and the supporting template files.

## Overview of the Pipeline

The generation process follows a deterministic six‑stage pipeline. Each stage is implemented in the `html_report` module and operates entirely within the local filesystem, ensuring full offline capability.

| Stage | Action | Key Source Reference |
|-------|--------|----------------------|
| **Ingest** | Load `documents/tracker.csv` into a DataFrame | [`tools/html_report.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tools/html_report.py) – `pd.read_csv` |
| **Clean** | Parse dates, fill missing values, derive metrics (e.g., days‑to‑response) | [`tools/html_report.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tools/html_report.py) – `pd.to_datetime` |
| **Aggregate** | Compute weekly counts, status distributions, and median response times | [`tools/html_report.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tools/html_report.py) – `DataFrame.groupby` |
| **Visualise** | Plot charts and save them as SVG markup | [`tools/html_report.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tools/html_report.py) – `plt.savefig(..., format='svg')` |
| **Templating** | Inject SVG strings and statistics into an HTML skeleton | [`tools/html_report.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tools/html_report.py) – `string.Template` |
| **Output** | Write the final file to [`reports/dashboard.html`](https://github.com/MadsLorentzen/ai-job-search/blob/main/reports/dashboard.html) | [`tools/html_report.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tools/html_report.py) – `Path.write_text` |

## Step‑by‑Step Implementation

### Step 1: Load the Tracker CSV

The command expects a CSV file at `documents/tracker.csv` containing columns such as `applied_on`, `response_on`, `company`, and `status`. It uses **pandas** to parse the file and immediately converts date columns to datetime objects.

```python
import pandas as pd
import pathlib

TRACKER = pathlib.Path("documents/tracker.csv")
df = pd.read_csv(TRACKER, parse_dates=["applied_on", "response_on"])

```

### Step 2: Clean and Enrich Data

After loading, the pipeline calculates derived fields to support time‑series analysis. It computes the difference between response and application dates and extracts ISO calendar weeks for grouping.

```python
df["days_to_response"] = (df["response_on"] - df["applied_on"]).dt.days
df["week"] = df["applied_on"].dt.isocalendar().week

```

### Step 3: Compute Summary Statistics

Aggregations drive the dashboard’s KPIs and charts. The code groups by week for trend lines and uses `value_counts()` for categorical breakdowns like application status.

```python
weekly_counts = df.groupby("week").size()
status_counts = df["status"].value_counts()
total_apps = len(df)

```

### Step 4: Generate Inline SVG Charts

Rather than saving separate image files, the tool captures SVG markup as strings. It sets matplotlib’s backend to SVG, plots the data, and writes the output to an in‑memory buffer. This keeps the final HTML self‑contained.

```python
import matplotlib.pyplot as plt
from io import StringIO

def plot_to_svg(series, title):
    fig, ax = plt.subplots(figsize=(8, 4))
    series.plot(kind="bar", ax=ax, color="steelblue")
    ax.set_title(title)
    buf = StringIO()
    fig.savefig(buf, format="svg", bbox_inches="tight")
    plt.close(fig)
    return buf.getvalue()

weekly_svg = plot_to_svg(weekly_counts, "Applications per Week")
status_svg = plot_to_svg(status_counts, "Status Distribution")

```

### Step 5: Assemble the HTML Template

The repository includes [`tools/html_report_template.html`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tools/html_report_template.html), a minimal HTML skeleton containing placeholders like `$weekly_chart` and `$total_apps`. The command uses Python’s `string.Template` to substitute the SVG strings and computed values, producing a single HTML document with inline vector graphics.

```python
from string import Template

template_path = pathlib.Path("tools/html_report_template.html")
html_template = Template(template_path.read_text())

report_html = html_template.substitute(
    weekly_chart=weekly_svg,
    status_chart=status_svg,
    total_apps=total_apps,
    last_update=pd.Timestamp.now().strftime("%Y-%m-%d %H:%M")
)

```

### Step 6: Write the Offline Dashboard

Finally, the tool writes the populated HTML to [`reports/dashboard.html`](https://github.com/MadsLorentzen/ai-job-search/blob/main/reports/dashboard.html). Because the SVG markup is embedded directly, opening this file in any browser requires no network requests.

```python
output_path = pathlib.Path("reports/dashboard.html")
output_path.write_text(report_html)

```

## CLI Usage and Command Flags

According to the source code in [`tools/html_report.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tools/html_report.py), the command exposes several options for customizing output:

- **`--output <path>`** – Specify a custom destination file (defaults to [`reports/dashboard.html`](https://github.com/MadsLorentzen/ai-job-search/blob/main/reports/dashboard.html)).
- **`--filter <status>`** – Restrict the analysis to rows matching a specific status value (e.g., "interviewing").
- **`--dry-run`** – Execute the data pipeline without writing the final file, useful for validation.

Invoke the tool from the repository root using the module execution syntax:

```bash
python -m ai_job_search html-report

```

Or, if the package is installed with console scripts:

```bash
ai-job-search html-report --output ./custom_report.html

```

Documentation for these flags resides in [`.claude/commands/html-report.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/commands/html-report.md).

## Summary

- **pandas** ingests and enriches the raw `documents/tracker.csv` data.
- Derived metrics (weeks, days‑to‑response) enable time‑series and categorical analysis.
- **matplotlib** renders charts as **SVG** strings captured in memory via `StringIO`.
- `string.Template` injects the SVG and statistics into [`tools/html_report_template.html`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tools/html_report_template.html).
- The resulting [`reports/dashboard.html`](https://github.com/MadsLorentzen/ai-job-search/blob/main/reports/dashboard.html) is a fully offline, self‑contained dashboard with no external dependencies.

## Frequently Asked Questions

### Does the dashboard require an internet connection to view charts?

No. The `/html-report` command embeds SVG markup directly into the HTML file. Because **Scalable Vector Graphics** are text‑based XML, they travel with the document, eliminating the need for external image hosts or CDN links.

### Can I customize the chart colors or the HTML layout?

Yes. The visual styling and HTML structure are controlled by [`tools/html_report_template.html`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tools/html_report_template.html) and the matplotlib parameters inside [`tools/html_report.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tools/html_report.py). Editing the CSS within the template or adjusting the `color` arguments in the plotting functions will change the dashboard appearance.

### What CSV columns are required for the report to work?

The pipeline expects at minimum `applied_on`, `response_on`, `company`, and `status` columns. The code uses `parse_dates` on the date fields, so ensure they are in a standard ISO or locale‑recognizable format. Missing optional columns are handled gracefully, but absence of `applied_on` will raise a `KeyError` during the grouping stage.

### Is there a way to automate this report generation, such as in a CI pipeline?

Yes. Because the tool is a standard CLI entry point, you can schedule it via cron or GitHub Actions. Use the `--dry-run` flag in automation to verify data integrity before overwriting [`reports/dashboard.html`](https://github.com/MadsLorentzen/ai-job-search/blob/main/reports/dashboard.html), and commit the generated HTML artifact if you wish to publish the dashboard via GitHub Pages.