From 848eddbecc6564ba5604b6513ee91149930ed51b Mon Sep 17 00:00:00 2001 From: Yuan Chen Date: Thu, 16 Jul 2026 15:48:59 -0400 Subject: [PATCH] feat(html-report): add /html-report command for application tracker dashboard (#131) Self-contained HTML dashboard generated from job_search_tracker.csv and the application archives: stat cards, status/sector/channel/funnel charts as hand-generated inline SVG (no CDN, fully offline), HTML-escaped interpolation throughout, and a filterable applications table. Includes Python guards for the command file and the reports/ gitignore rule. --- .claude/commands/html-report.md | 135 ++++++++++++++++++++++++++++++ .gitignore | 3 + README.md | 4 +- tests/test_html_report_command.py | 79 +++++++++++++++++ 4 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 .claude/commands/html-report.md create mode 100644 tests/test_html_report_command.py diff --git a/.claude/commands/html-report.md b/.claude/commands/html-report.md new file mode 100644 index 0000000..7fff2e4 --- /dev/null +++ b/.claude/commands/html-report.md @@ -0,0 +1,135 @@ +# /html-report - Generate Application Tracker Dashboard + +Generate a self-contained HTML dashboard from `job_search_tracker.csv` and the application archives under `documents/applications/`. The output is a single `.html` file — no server, no dependencies — that can be opened directly in a browser. + +## Step 0: Parse Arguments + +- No argument → output to `reports/application-dashboard.html` +- A path argument (e.g. `/html-report ~/Desktop/report.html`) → use that path +- `--open` flag → after writing, tell the user to open the file (cannot open a browser directly) + +Create `reports/` if it does not exist. + +--- + +## Step 1: Collect Data + +Read in parallel: + +1. **`job_search_tracker.csv`** — the primary source. Parse every row into a record with fields: + `date`, `company`, `sector`, `role`, `role_type`, `channel`, `status`, `contact_person`, `fit_rating`, `notes`, `cv_file`, `cover_letter_file`, `source` + +2. **`documents/applications/*/outcome.md`** — for each resolved application, read the outcome file to get the exact interview stages reached (the checkboxes) and any notes. Merge this into the matching tracker row by company+role fuzzy match (lowercase, ignore punctuation). If an archive exists for a row but there is no match, attach it as extra context anyway. + +Status normalisation — map tracker values to five canonical buckets before computing stats: +- `applied` → **Active** (resume submitted, no further signal) +- `interview` → **Interview** +- `offer` → **Offer** +- `hired` → **Hired** +- `rejected` / `no_response` / `no response` / `offer_declined` / `interview_only` / `withdrawn` → **Rejected/Closed** + +--- + +## Step 2: Compute Summary Stats + +From the normalised data compute: + +- **Total applications** +- **By status bucket:** count per bucket +- **By sector:** count per unique sector value +- **By channel:** online vs referral vs other +- **By year/season:** group by the `date` field (which may be a year like `2025` or a full date) +- **Funnel rates:** what % progressed past resume screen (reached Interview or beyond) +- **Rejection rate:** Rejected/Closed ÷ Total with a resolved status (exclude Active) + +--- + +## Step 3: Generate the HTML + +Write a single self-contained HTML file. All CSS is inline in a `