From 2c3d2d85589d294499cb22c420553de5552e316a Mon Sep 17 00:00:00 2001 From: Mads Lorentzen Date: Wed, 19 Aug 2026 20:54:46 +0200 Subject: [PATCH] fix(html-report): funnel from stage history, rejection rate from true rejections The funnel was computed from current status - a state, not a history - so an application that interviewed and was later rejected never counted as reaching Interview, and a hired candidate produced Interview=0. The stage checkboxes Step 1.2 already merges from outcome.md are the history; Step 2 and chart 4 now use them. The rejection rate also counted offer_declined (a success) and withdrawn (candidate-initiated) as rejections and left unresolved Interview/Offer rows in the denominator. Review findings F10 and F11 (2026-08-19). Co-Authored-By: Claude Opus 5 (1M context) --- .claude/commands/html-report.md | 6 ++--- CHANGELOG.md | 9 ++++++++ tests/test_html_report_command.py | 37 +++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/.claude/commands/html-report.md b/.claude/commands/html-report.md index a264d0d..ec1e814 100644 --- a/.claude/commands/html-report.md +++ b/.claude/commands/html-report.md @@ -49,8 +49,8 @@ From the normalised data compute: - **By sector:** count per unique sector value - **By channel:** portal vs 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) +- **Funnel rates:** what % progressed past resume screen (reached Interview or beyond). Compute stage-reached from history, not current status: an application counts as having reached a stage when its current status implies it **or** its merged `outcome.md` stage checkboxes (Step 1.2) show the stage was reached - a `rejected` row whose outcome file ticks an interview stage reached Interview, and a `hired` row reached every stage before Hired. Current status alone structurally undercounts every earlier stage: a finished search would read as though nobody ever interviewed. +- **Rejection rate:** true rejections (`rejected`, `no_response`) ÷ applications with a final outcome. `offer_declined` (the candidate turned the offer down - a success) and `withdrawn` (candidate-initiated) are not rejections and stay out of the numerator; Interview and Offer rows are still unresolved, so they stay out of the denominator along with Active. The Rejected/Closed status *bucket* still groups all closed rows for the doughnut - the rate just must not reuse the bucket blindly. --- @@ -106,7 +106,7 @@ Write a single self-contained HTML file. All CSS is inline in a `