Data and analytics

Newsletter topic click and unsubscribe analytics dashboard

A beginner's pandas pipeline that ranks newsletter topics by click rate and charts unsubscribes by weekday and hour from an exported ESP CSV.

pythonpandasmatplotlibpytestruff

Built 1 time, downloaded 0 times.

Install from your terminal

Run this in your project folder. It signs you in, shows what changes and asks before it overwrites a file.

npx claude-lab add newsletter-topic-click-and-unsubscribe-analytics-dashboard-396a7e
CLAUDE.md
# Newsletter stats

## Purpose

This project ranks newsletter topics by click-through and shows when subscribers unsubscribe, by weekday and hour. The user is a beginner solo analyst, so keep explanations plain and steps small.

## Stack

- Python 3.12, pandas, matplotlib, pytest, ruff.
- No database and no web server. Data lives in CSV files under `data/raw/` (real exports, never committed) and `data/sample/` (fake data for tests and checks).
- Ask before adding a database, a web server, or any new dependency.

## Commands

- Create the virtual environment: `python -m venv .venv`
- Activate it (macOS/Linux): `source .venv/bin/activate` (Windows: `.venv\Scripts\activate`)
- Install dependencies: `pip install -r requirements.txt`
- Run the analysis and regenerate reports: `python -m newsletter_stats.report`
- Run tests: `pytest -q`
- Lint: `ruff check .`

## Layout

- `src/newsletter_stats/` holds the package. `parse.py` extracts topics, `clean.py` parses dates, `report.py` builds outputs.
- `tests/` holds pytest files. Each parser needs at least one messy-input test.
- `reports/` holds generated outputs (`topics.csv`, `unsubscribes.png`, the markdown report).
- `data/sample/` holds fake campaign and event rows with hashed subscriber IDs.
- `data/raw/` holds real exports. It is gitignored.

## Data rules

- Never commit subscriber emails, names, or any personal field. Never print or paste raw exports into chat or output.
- Use hashed subscriber IDs in every output file, chart, and fixture.
- Raw exports go in `data/raw/`, and that folder stays out of git.
- Click rate is unique clickers divided by delivered. Unsubscribe rate is unsubscribes divided by delivered, per send.
- Topics must have at least 200 delivered before they can be ranked.

## Topic parsing

- Each send has a topic tag in its campaign name.
- Parse topics only in `parse()` inside `src/newsletter_stats/parse.py`. Do not parse topic strings anywhere else, including tests, reports, or notebooks.

## Timezone basis

- Unsubscribe timing uses the send timestamp in UTC. If the subscriber has a local timezone field, convert to that timezone. Otherwise keep UTC.
- Every chart title must state the basis, for example "Unsubscribes by weekday and hour (subscriber local time)" or "(UTC, no timezone data)".

## Gotchas

- ESP exports mix date formats. Always parse with an explicit format string in `clean.py`.
- On an unknown date format, raise an error that names the value and the column. Do not guess, and do not silently drop rows.
- Check row counts before and after each cleaning step, and report any drop.
- Campaign and event IDs must match across both CSVs. Report orphan rows instead of ignoring them.

## Review

@REVIEW.md

## Working style

- Make small changes and run `ruff check .` and `pytest -q` after each one.
- Explain each change in plain language in the final message. The user is new to coding, so define any term the first time you use it.
- When a request is unclear, ask one question before writing code.
Use for my project